Avalonia开发实践(二)——开发带边框的Grid

news2024/9/23 15:19:14

一、开发背景

在实际开发工作中,常常会用到Grid进行布局。为了美观考虑,会给每个格子加上边框,如下图:

原生的Grid虽然有ShowGridLines属性可以控制显示格子之间的线,但线的样式不能定义,可以说此功能非常鸡肋。接下来我们自己动手实现Grid中的网格线!

二、设计思路

虽然Grid自带的格子线非常拉胯,但它的实现方式为我们提供了宝贵的思路。

首先,Grid继承自Panel,而在Panel中,Render方法已经密封了,所以想在Grid中利用Render方法进行边框绘制这条路就走不通了。

官方的做法是,定义一个GridLinesRenderer,继承自Control。将GridLinesRenderer添加到Grid的VisualChildren中,在GridLinesRenderer的Render方法中实现对Grid的边框绘制。

三、实现过程

1、定义边框绘制类

/// <summary>
/// GridLineStyle
/// </summary>
internal class GridLinesRenderer : Control
{
    private Pen _borderPen;
    private Size _lastArrangeSize;

    public override void Render(DrawingContext context)
    {
        base.Render(context);
        var grid = this.GetVisualParent<Grid>();
        if (grid == null || !grid.ShowGridLines)
            return;

        if (_borderPen == null)
        {
            _borderPen = new Pen(grid.GridLineBrush, grid.GridLineWidth, lineCap: PenLineCap.Round);
        }
        else
        {
            _borderPen.Brush = grid.GridLineBrush;
            _borderPen.Thickness = grid.GridLineWidth;
        }

        // 获取行高、列宽数据
        var rowHeightArr = new double[Math.Max(grid.RowDefinitions.Count, 1)];
        var colWidthArr = new double[Math.Max(grid.ColumnDefinitions.Count, 1)];
        if (grid.RowDefinitions.Count == 0)
        {
            rowHeightArr[0] = _lastArrangeSize.Height;
        }
        else
        {
            for (int i = 0; i < grid.RowDefinitions.Count; i++)
            {
                rowHeightArr[i] = grid.RowDefinitions[i].ActualHeight;
            }
        }
        if (grid.ColumnDefinitions.Count == 0)
        {
            colWidthArr[0] = _lastArrangeSize.Width;
        }
        else
        {
            for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
            {
                colWidthArr[i] = grid.ColumnDefinitions[i].ActualWidth;
            }
        }

        // 绘制内边框
        var _lastOffsetX = 0d;
        var _currentOffsetX = colWidthArr[0];
        for (int i = 1; i < colWidthArr.Length; ++i)
        {
            if (_lastOffsetX != _currentOffsetX)
            {
                DrawGridLine(
                    context,
                    _currentOffsetX, 0.0,
                    _currentOffsetX, _lastArrangeSize.Height);
                _lastOffsetX = _currentOffsetX;
            }
            _currentOffsetX += colWidthArr[i];
        }

        var _lastOffsetY = 0d;
        var _currentOffsetY = rowHeightArr[0];
        for (int i = 1; i < rowHeightArr.Length; ++i)
        {
            if (_lastOffsetY != _currentOffsetY)
            {
                DrawGridLine(
                    context,
                    0.0, _currentOffsetY,
                    _lastArrangeSize.Width, _currentOffsetY);
            }
            _currentOffsetY += rowHeightArr[i];
        }

        // 绘制外边框
        double radiusX = grid.CornerRadius;
        double radiusY = grid.CornerRadius;
        Rect rect = new Rect(_lastArrangeSize).Deflate(grid.GridLineWidth / 2);
        if (radiusX == 0.0 && radiusY == 0.0)
        {
            var rectangleGeometry = new RectangleGeometry(rect);
            context.DrawGeometry(null, _borderPen, rectangleGeometry);
        }
        else
        {
            StreamGeometry streamGeometry = new StreamGeometry();
            using (StreamGeometryContext streamGeometryContext = streamGeometry.Open())
            {
                GeometryHelper.DrawRoundedCornersRectangle(streamGeometryContext, rect, radiusX, radiusY);
            }
            context.DrawGeometry(null, _borderPen, streamGeometry);
        }
    }

    private void DrawGridLine(
        DrawingContext drawingContext,
        double startX,
        double startY,
        double endX,
        double endY)
    {
        var start = new Point(startX, startY);
        var end = new Point(endX, endY);
        drawingContext.DrawGeometry(null, _borderPen, new RectangleGeometry(new Rect(start, end).Deflate(0.5)));
    }

    internal void UpdateRenderBounds(Size arrangeSize)
    {
        _lastArrangeSize = arrangeSize;
        InvalidateVisual();
    }
}

2、自定义Grid,重写ArrangeOverride方法,每次布局变化时触发边框重绘方法

/// <summary>
/// Grid
/// </summary>
public class Grid : Avalonia.Controls.Grid
{
    private GridLinesRenderer _gridLinesRenderer;

    /// <summary>
    /// Defines the <see cref="ShowGridLines"/> property.
    /// </summary>
    public new bool ShowGridLines
    {
        get { return (bool)GetValue(ShowGridLinesProperty); }
        set { SetValue(ShowGridLinesProperty, value); }
    }

    /// <summary>
    /// Defines the <see cref="ShowGridLinesProperty"/> property.
    /// </summary>
    public static readonly new StyledProperty<bool> ShowGridLinesProperty = AvaloniaProperty.Register<Grid, bool>("ShowGridLines");

    /// <summary>
    /// Defines the <see cref="GridLineBrush"/> property.
    /// </summary>
    public IBrush GridLineBrush
    {
        get { return (IBrush)GetValue(GridLineBrushProperty); }
        set { SetValue(GridLineBrushProperty, value); }
    }

    /// <summary>
    /// Defines the <see cref="GridLineBrushProperty"/> property.
    /// </summary>
    public static readonly StyledProperty<IBrush> GridLineBrushProperty = AvaloniaProperty.Register<Grid, IBrush>("GridLineBrush");

    /// <summary>
    /// Defines the <see cref="GridLineWidth"/> property.
    /// </summary>
    public double GridLineWidth
    {
        get { return (double)GetValue(GridLineWidthProperty); }
        set { SetValue(GridLineWidthProperty, value); }
    }

    /// <summary>
    /// Defines the <see cref="GridLineWidthProperty"/> property.
    /// </summary>
    public static readonly StyledProperty<double> GridLineWidthProperty = AvaloniaProperty.Register<Grid, double>("GridLineWidth", 1d);

    /// <summary>
    /// Defines the <see cref="CornerRadius"/> property.
    /// </summary>
    public float CornerRadius
    {
        get { return (float)GetValue(CornerRadiusProperty); }
        set { SetValue(CornerRadiusProperty, value); }
    }

    /// <summary>
    /// Defines the <see cref="CornerRadiusProperty"/> property.
    /// </summary>
    public static readonly StyledProperty<float> CornerRadiusProperty = AvaloniaProperty.Register<Grid, float>("CornerRadius");

    private GridLinesRenderer EnsureGridLinesRenderer()
    {
        if (ShowGridLines && _gridLinesRenderer == null)
        {
            _gridLinesRenderer = new GridLinesRenderer();
            VisualChildren.Add(_gridLinesRenderer);
        }

        if (!ShowGridLines && _gridLinesRenderer != null)
        {
            VisualChildren.Remove(_gridLinesRenderer);
            _gridLinesRenderer = null;
        }
        return _gridLinesRenderer;
    }

    /// <summary>
    /// ArrangeOverride
    /// </summary>
    /// <param name="arrangeSize"></param>
    /// <returns></returns>
    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var size = base.ArrangeOverride(arrangeSize);
        var gridLinesRenderer = EnsureGridLinesRenderer();
        gridLinesRenderer?.UpdateRenderBounds(arrangeSize);
        return size;
    }
}

这样,一个具有边框绘制功能的Grid就完成了。

四、进阶思考

有时Grid并不是所有单元格都用得上的,可能还涉及跨行跨列的情况,这时就需要根据每个子元素的空间占据大小来绘制边框了。我们可以记录Grid每个单元格的布局参数,再遍历每个子元素,用合并单元格的思路来绘制内部边框。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1910708.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人声提取软件有哪些?4种人声提取软件轻松提取人声

在数字音乐与视频制作日益盛行的今天&#xff0c;人声提取软件成为了许多创作者不可或缺的工具。无论是想要从复杂的音乐中分离出纯净的人声&#xff0c;还是希望从视频中提取出精彩的对话片段&#xff0c;一款简单有效的人声提取工具尤为重要&#xff0c;下面给大家分享4种简单…

WTM的项目中EFCore如何适配人大金仓数据库

一、WTM是什么 WalkingTec.Mvvm框架&#xff08;简称WTM&#xff09;最早开发与2013年&#xff0c;基于Asp.net MVC3 和 最早的Entity Framework, 当初主要是为了解决公司内部开发效率低&#xff0c;代码风格不统一的问题。2017年9月&#xff0c;将代码移植到了.Net Core上&…

三菱FX3U进阶课程-运动控制讲解

如果你不会用三菱FX3U系列plc做运动控制&#xff0c;不会控制步进电机、不会控制伺服电机&#xff0c;那来学习本课程就对了&#xff0c;课程带你的价值是&#xff1a; 1、究竟PLC是怎么控制得了步进电机、伺服电机的&#xff1f;好奇怪啊&#xff0c;为啥别人会&#xff0c;我…

WANGLS

DHCP 动态主机配置协议 原理 网络 网络是双向的,网络是有方向的 广播;广播是由种类的,广播是有范围的的 租约的建立——租约的相应、租约的选择——租约的完成 租约的建立:租约的请求 有客户端发出 DHCP discover 广播、寻找服务器 租约的响应 收到响应,不是服务器,…

Revit 2025:建筑设计师的得力助手

在这繁忙的现代社会中&#xff0c;建筑设计师们总是追求着更高效、更精确的工具。而Revit 2025&#xff0c;正如一位老友般&#xff0c;默默地陪伴在我们身边&#xff0c;帮助我们实现心中的蓝图。今天&#xff0c;我怀着满腔的热情与感激&#xff0c;向大家介绍这款软件的功能…

越低越好?所以伦敦金至少可以入金多少呢?

投资者要进行黄金投资&#xff0c;市场中有多个品种可以选择。笔者认为&#xff0c;在众多品种之中&#xff0c;伦敦金是一种适应性比较广的黄金投资方式。如果投资者要选择伦敦金作为自己的黄金投资的主要方式&#xff0c;那么有一些就基本的问题是我们需要了解的&#xff0c;…

Retrofit框架源码深度剖析【Android热门框架分析第二弹】

Android热门框架解析&#xff0c;你确定不来看看吗&#xff1f; OkHttp框架源码深度剖析【Android热门框架分析第一弹】 Retrofit框架源码深度剖析【Android热门框架分析第二弹】 什么是Retrofit&#xff1f; 准确来说&#xff0c;Retrofit 是一个 RESTful 的 HTTP 网络请求…

R包:reticulate R对python的接口包

介绍1 R和python是两种不同的编程语言&#xff0c;前者是统计学家发明并且服务数学统计计算&#xff0c;后者则是最万能的胶水语言。随着大数据时代的到来&#xff0c;两者在数据分析领域存在越来越多的共同点且可以相互使用&#xff0c;为了破解二者的编程壁垒&#xff0c;CR…

Noah-MP陆面生态水文模拟与多源遥感数据同化

陆面模型在生态水文研究中的地位和作用&#xff1b;熟悉模型的发展历程&#xff0c;常见模型及各自特点&#xff1b;理解Noah-MP模型的原理&#xff0c;掌握Noah-MP模型在单站和区域的模拟、模拟结果的输出和后续分析及可视化等方法&#xff1b;课程还将深入讲解数据同化的原理…

从天空到地面:无人机航拍推流直播技术在洞庭湖决口封堵中的全方位支援

据新闻报道&#xff0c;受持续强降雨影响&#xff0c;湖南省华容县团洲垸洞庭湖一线堤防发生管涌险情&#xff0c;随后出现决口。截至7月8日20时左右&#xff0c;226米长的洞庭湖一线堤防决口已累计进占208米&#xff0c;目前剩余18米&#xff0c;有望在今晚或9日凌晨实现合龙。…

Spring Cloud Alibaba -- 分布式定时任务解决方案(轻量级、快速构建)(ShedLock 、@SchedulerLock )

文章目录 一、 ShedLock简介二、 SchedulerLock三、基于Mysql方式使用步骤1.建表2.引入依赖3.Mysql连接配置4.ScheduledLock配置5.启动类配置6.创建定时任务7.启动多个项目服务进行测试8.SchedulerLock注解说明 四、使用注意事项 一、 ShedLock简介 ShedLock 是一个用于 Java …

swiftui给视图添加边框或者只给某个边设置border边框

直接使用border()就可以给一个视图添加边框效果&#xff0c;但是这种边框会给所有的边都设置上。 border()里面也可以添加属性.border(.blue, width: 5)这种就是设置颜色和宽度。 设置圆角边框 Text("1024小神").padding().cornerRadius(20).overlay(RoundedRectang…

如何在Facebook上保护你的个人资料安全?

随着社交媒体的普及和个人信息的数字化&#xff0c;保护个人资料安全成为越来越重要的议题。特别是在使用像Facebook这样的平台时&#xff0c;我们需要特别注意如何保护我们的数据免受未经授权的访问和滥用。本文将探讨一些实用的方法&#xff0c;以及如何增强你在Facebook上的…

keil mdk注释插件合集格式、时间、日期注释

文章目录 一、前言二、安装步骤2.1 解压tools.zip2.2 tools 文件解释2.3 添加注释带keil 三、配置3.1 格式化代码3.2 文件注释3.3函数注释3.4 当前日期3.5 当前时间 四、编辑注释模板4.1 编辑函数注释模板4.2 编辑C文件注释模板4.3 编辑h文件注释模板 五、为注释功能添加快捷键…

qmt量化交易策略小白学习笔记第55期【qmt编程之期权数据--获取历史期权列表】

qmt编程之获取期权数据 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 感谢关注&#xff0c;咨询免费开通量化回测与获取实盘权限&#xff0c;欢迎和博主联系&#xff01; 获取历史期权列表 …

Sui DeFi现状介绍

关于Sui Network Sui是基于第一原理重新设计和构建而成的L1公有链&#xff0c;旨在为创作者和开发者提供能够承载Web3中下一个十亿用户的开发平台。Sui上的应用基于Move智能合约语言&#xff0c;并具有水平可扩展性&#xff0c;让开发者能够快速且低成本支持广泛的应用开发。获…

视频监控管理平台智能边缘分析一体机视频监控系统客流统计检测算法

在当今数据驱动的时代&#xff0c;客流统计作为商业分析的重要手段&#xff0c;其准确性和实时性对于商家决策具有至关重要的影响。随着技术的发展&#xff0c;智能边缘分析一体机结合了边缘计算与深度学习技术&#xff0c;为客流统计提供了更为高效、精准的解决方案。 首先&am…

可变参数 Collections 不可变集合 Stream流

目录 1.可变参数&#xff1a; 2.Collections: 3.不可变集合&#xff1a; 4.Stream流: 1、什么是流 2、如何生成流 1.单列集合获取Stream流 2.双列集合获取Stream流 3.数组获取Stream流&#xff1a; 4.一堆零散数据&#xff1a; Stream接口中的静态方法 3.Stream流的…

【总线】AXI第九课时:介绍AXI响应信号 (Response Signaling):RRESP和 BRESP

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

如何在 Odoo 16 中配置搜索视图、过滤器和分组

Odoo 中有多种视图类型&#xff0c;包括表单、看板、树、日历、Qweb、搜索等。与表单视图相比&#xff0c;搜索视图用于过滤其他视图的内容&#xff0c;而不是显示内容本身。这使得搜索视图与其他视图不同。表单视图仅包含一条记录&#xff0c;因此不需要搜索。 本文将详细介绍…