当前位置: 首页 > news >正文

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

一、开发背景

在实际开发工作中,常常会用到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每个单元格的布局参数,再遍历每个子元素,用合并单元格的思路来绘制内部边框。

相关文章:

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

一、开发背景 在实际开发工作中&#xff0c;常常会用到Grid进行布局。为了美观考虑&#xff0c;会给每个格子加上边框&#xff0c;如下图&#xff1a; 原生的Grid虽然有ShowGridLines属性可以控制显示格子之间的线&#xff0c;但线的样式不能定义&#xff0c;可以说此功能非常…...

Java泛型的定义与运用

泛型 泛型的作用从使用层面上来说是统一数据类型&#xff0c;防止将来的数据转换异常。从定义层面上来说&#xff0c;定义带泛型的类&#xff0c;方法等&#xff0c;将来使用的时候给泛型确定什么类型&#xff0c;泛型就会变成什么类型&#xff0c;凡是涉及到泛型的都会变成确…...

Java如何自定义注解及在SpringBoot中的应用

注解 注解&#xff08;Annotation&#xff09;&#xff0c;也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性&#xff0c;与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面&#xff0c;用来对这些元素进行说…...

微软 Edge 浏览器全解析

微软 Edge 是微软推出的一个现代化浏览器,继承了 Internet Explorer(IE)的部分功能,但在速度、安全性和兼容性方面做出了很大改进。下面是对微软 Edge 浏览器的详细解析,包括其特点、安装、配置和常见问题的解答。 微软 Edge 浏览器的特点 基于 Chromium 内核 Edge 浏览…...

C++ 八股(1)

C语言中strcpy为什么不安全&#xff1f;如何解决&#xff1f; 主要原因是缺乏对输入长度的边界检查&#xff0c;容易导致缓冲区溢出漏洞。 解决&#xff1a;可以使用strncpy函数替代&#xff0c;或者在程序最顶端加入代码段 #define _CRT_SECURE_NO_WARNINGS 缓冲区溢出 …...

超高精电容传感器PCAP01调试+LABVIEW数据可视化调试手记

PCAP01超高精电容传感芯片STM32LabView可视化 文章目录 PCAP01超高精电容传感芯片STM32LabView可视化一、PCAP01介绍1.1、PCAP01引脚定义1.2、电容测量1.3、温度测量1.4、PCAP典型测试电路 二、PCAP01的STM32驱动2.1、SPI协议配置2.2、PCAP01浮空电容测量内部温度测量操作流程 …...

5.更多

发现一个项目与 MkDocs 类似的项目 PyMdown 拓展文档 &#xff0c;等待探索。 1.排版模仿 以下网站使用 MkDocs 构建 Material for MkDocs 的美化 - Charles Les Notebook (charleschile.com) Documentation - Home Assistant (home-assistant.io) Godot Docs – master bra…...

ConditionalOnJndi注解使用介绍、应用场景以及示例代码

概述 ConditionalOnJndi 是 Spring Framework 中的一个条件注解&#xff0c;用于在特定的 JNDI (Java Naming and Directory Interface) 环境条件下决定是否创建一个 bean 或配置一个 bean。JNDI 是 Java EE 规范中定义的一种用于访问命名和目录服务的 API&#xff0c;它允许 …...

Spring Cloud 引入

1.单体架构&#xff1a; 定义&#xff1a;所有的功能实现都打包成一个项目 带来的后果&#xff1a; ①后端服务器的压力越来越大&#xff0c;负载越来越高&#xff0c;甚至出现无法访问的情况 ②业务越来越复杂&#xff0c;为了满足用户的需求&#xff0c;单体应用也会越来越…...

自定义波形图View,LayoutInflater动态加载控件保存为本地图片

效果图&#xff1a; 页面布局&#xff1a; <?xml version"1.0" encoding"utf-8"?><LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"android:la…...

每日一道算法题 求最小公倍数

题目 求最小公倍数_牛客题霸_牛客网 (nowcoder.com) Python 辗转相除法 dividend,divisormap(int,input().split()) #被除数&#xff0c;除数 # remainder0 余数 # 最小公倍数 def lcm(dividend,divisor):# 最大公约数def gcd(dividend,divisor):if 0divisor:return divid…...

【OCC学习18】三维几何对象工具包:TKG3d

【OCC学习18】三维几何对象工具包&#xff1a;TKG3d loveoobaby 已于 2022-08-26 10:10:32 修改 阅读量1.2k 收藏 10 点赞数 1 分类专栏&#xff1a; OpenCascade学习笔记 文章标签&#xff1a; 学习 版权 OpenCascade学习笔记 专栏收录该内容 24 篇文章60 订阅 订阅专栏…...

【Unix】SunOS/Oracle Solaris系统介绍

一.SunOS系统介绍 SunOS 是由 Sun Microsystems 开发的 Unix 操作系统。它最初是为 Sun 的 SPARC 架构计算机设计的&#xff0c;后来也支持了 Intel x86 架构。SunOS 是基于 UNIX System V 4.1 版本&#xff0c;并且随着时间的发展&#xff0c;SunOS 经历了多个版本迭代&#…...

氛围感视频素材高级感的去哪里找啊?带氛围感的素材网站库分享

亲爱的创作者们&#xff0c;大家好&#xff01;今天我们来聊聊视频创作中至关重要的一点——氛围感。一个好的视频&#xff0c;不仅要有视觉冲击力&#xff0c;还要能够触动观众的情感。那我们应该去哪里寻找这些充满氛围感且高级的视频素材呢&#xff1f;别急&#xff0c;我这…...

基于Java的学生选课系统

第1章 系统概述 1.1概述 背景&#xff1a;随着计算机网络技术的发展&#xff0c;Web 数据库技术已成为应用最为广泛的网站架构基础技术。学生选课系统作为教育单位不可缺少的部分&#xff0c;其内容对于学校的决策者和管理者至关重要。传统的人工管理方式存在效率低、保密性差等…...

802.11漫游流程简单解析与笔记_Part2_05_wpa_supplicant如何通过nl80211控制内核开始关联

最近在进行和802.11漫游有关的工作&#xff0c;需要对wpa_supplicant认证流程和漫游过程有更多的了解&#xff0c;所以通过阅读论文等方式&#xff0c;记录整理漫游相关知识。Part1将记录802.11漫游的基本流程、802.11R的基本流程、与认证和漫游都有关的三层秘钥基础。Part1将包…...

STM32的 DMA(直接存储器访问) 详解

STM32的DMA&#xff08;Direct Memory Access&#xff0c;直接存储器存取&#xff09;是一种在单片机中用于高效实现数据传输的技术。它允许外设设备直接访问RAM&#xff0c;不需要CPU的干预&#xff0c;从而释放CPU资源&#xff0c;提高CPU工作效率&#xff0c;本文基于STM32F…...

14-65 剑和诗人39 - 打造你自己的 Devin

​​​​​ 绝密 Devin 架构 更具体地说,构建您自己的 AI 代理。 Devin 使用 GPT-4 ,而人们已经开始用 Claude-3-Opus 构建替代方案 Devin 的 UI 体验更好。 例如,它甚至看不到浏览器,但它确实存在于用户面前 此外,你可以随时与它“交谈”,就像与人交谈一样,它会在后…...

JavaScript 把CSDN博客内容存成PDF

F12 - 控制台 -命令行 输入执行&#xff1a;允许粘贴输入执行代码&#xff1a; (function () {use strict;var articleBox $("div.article_content");articleBox.removeAttr("style");var head_str "";var foot_str "";var older…...

uniapp——银行卡号脱敏

样式 代码 {{bankNumber.replace(/(\d{4})(?\d)/g, "●●●● ").replace(/(\d{2})(?\d{2}$)/, " $1")}} 将银行卡号按照每四位一组的方式进行处理&#xff0c;前面的变成 剩下的正常显示...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...