(WPF学习记录)第十二章 自定义面板
2009-06-04 14:59
405 查看
Panel最大的好处是,它有Children属性可以进行孩子的排序。此属性的类别是UIElementCollection,负责处理对AddVisualChild、AddLogicalChild、RemoveVisualChild和RemoveLogicalChild,以进行孩子的增删。
第01个程序:统一的Grid
最简单的面板是UniformGrid。每个格子都是一样的宽和高。下面的类仿造UniformGrid的功能。
第1个文件:UniformGridAlmost.cs。
第2个文件:DuplicateUniformGrid.cs。
运行效果如下:
第02小程序:Canvas上绘图(PaintOnCanvasClone.cs)
Canvas在第七章介绍过,此面板类似传统的图形环境,利用坐标位置,指定元素的摆放地点。当有多个元素时,越早加入其中的元素出现在越下层(可被后加入的元素覆盖)。
运行结果:
第03个小程序:依对角线排列按钮
在Panel内添加Child时,一般的做法是:pnl.Children.Add(child),这样有件事不对劲,定义此child字段和属性的类,不知道有什么东西被加入或者删除了,也不知道什么时候调用的AddVisualChild、AddLogiclChild等。
一个可能的解决之道是写一个类似UIElementCollection的类,处理这件事,或者通知面板去做这件事。
第1个代码文件:DiagonalizeTheButtons.cs
有了这个类,加入孩子的时候就可以直接写pal.Add(child)。
第2个代码文件:DiagonalPanel.cs
运行效果:
第04个小程序:圆形排列按钮
一个圆可以被切割成多个扇形的小块。想将element排列成圆形,或许最直接的方式是让第个element都摆设在自己的小块内。
如下图所示,是一个小的扇形块,矩形表示按钮,从中心连到element右上角的这一条线,就是半径。
计算公式如下:
第01个代码文件:RadialPanelOrientation.cs
因为按钮通常都是宽(W)大于高(H),若按钮摆放方式不同(如旋转90度),这会影响半径的计算,不同的摆放方式会表现出来“以高度为主要决定因素”和“以宽度为主要决定因素”计算的半径。
第02个代码文件:RadialPanel.cs
第03个代码文件:CircleTheButtons.cs
运行效果如下:
第01个程序:统一的Grid
最简单的面板是UniformGrid。每个格子都是一样的宽和高。下面的类仿造UniformGrid的功能。
第1个文件:UniformGridAlmost.cs。
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; namespace Chapter12 { class UniformGridAlmost : Panel { public static readonly DependencyProperty ColumnsProperty; static UniformGridAlmost() { ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(UniformGridAlmost), new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure)); } public int Columns { set { SetValue(ColumnsProperty, value); } get { return (int)GetValue(ColumnsProperty); } } public int Rows { get { return (InternalChildren.Count + Columns - 1) / Columns; } } protected override Size MeasureOverride(Size sizeAvailable) { // 根据统一的行与列,计算孩子的尺寸 Size sizeChild = new Size(sizeAvailable.Width / Columns, sizeAvailable.Height / Rows); // 用来累积最大宽度和高度的变量 double maxwidth = 0; double maxheight = 0; foreach (UIElement child in InternalChildren) { // 调用每个孩子的Measure child.Measure(sizeChild); // 为孩子设置适合的尺寸 maxwidth = Math.Max(maxwidth, child.DesiredSize.Width); maxheight = Math.Max(maxheight, child.DesiredSize.Height); } // 为grid本身计算尺寸 return new Size(Columns * maxwidth, Rows * maxheight); } protected override Size ArrangeOverride(Size sizeFinal) { Size sizeChild = new Size(sizeFinal.Width / Columns, sizeFinal.Height / Rows); for (int index = 0; index < InternalChildren.Count; index++) { int row = index / Columns; int col = index % Columns; // 计算每个孩子在sizeFinal内的矩形 Rect rectChild = new Rect(new Point(col * sizeChild.Width, row * sizeChild.Height), sizeChild); // 然后调用Arrange更新布局 InternalChildren[index].Arrange(rectChild); } return sizeFinal; } } }
第2个文件:DuplicateUniformGrid.cs。
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; namespace Chapter12 { public class DuplicateUniformGrid : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new DuplicateUniformGrid()); } public DuplicateUniformGrid() { Title = "完全相同的grid"; Width = 480; Height = 288; WindowStartupLocation = WindowStartupLocation.CenterScreen; UniformGridAlmost unigrid = new UniformGridAlmost(); unigrid.Columns = 5; Content = unigrid; // 用随机尺寸的按钮,填入UniformGridAlmost Random rand = new Random(); for (int index = 0; index < 25; index++) { Button btn = new Button(); btn.Name = "按钮" + (index + 1); btn.Content = btn.Name; btn.FontSize += rand.Next(10); unigrid.Children.Add(btn); } AddHandler(Button.ClickEvent, new RoutedEventHandler(ButtonOnClick)); } void ButtonOnClick(object sender, RoutedEventArgs args) { Button btn = args.Source as Button; MessageBox.Show(btn.Name + "被点击!", Title); } } }
运行效果如下:
第02小程序:Canvas上绘图(PaintOnCanvasClone.cs)
Canvas在第七章介绍过,此面板类似传统的图形环境,利用坐标位置,指定元素的摆放地点。当有多个元素时,越早加入其中的元素出现在越下层(可被后加入的元素覆盖)。
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; namespace Chapter12 { public class PaintOnCanvasClone : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new PaintOnCanvasClone()); } public PaintOnCanvasClone() { Title = "Canvas上的矩形"; Width = 480; Height = 320; WindowStartupLocation = WindowStartupLocation.CenterScreen; Canvas canv = new Canvas(); Content = canv; SolidColorBrush[] brushes = { Brushes.Red, Brushes.Green, Brushes.Blue }; for (int i = 0; i < brushes.Length; i++) { Rectangle rect = new Rectangle(); rect.Fill = brushes[i]; rect.Width = 100; rect.Height = 100; canv.Children.Add(rect); Canvas.SetLeft(rect, 50 * (i + 1)); Canvas.SetTop(rect, 50 * (i + 1)); } } } }
运行结果:
第03个小程序:依对角线排列按钮
在Panel内添加Child时,一般的做法是:pnl.Children.Add(child),这样有件事不对劲,定义此child字段和属性的类,不知道有什么东西被加入或者删除了,也不知道什么时候调用的AddVisualChild、AddLogiclChild等。
一个可能的解决之道是写一个类似UIElementCollection的类,处理这件事,或者通知面板去做这件事。
第1个代码文件:DiagonalizeTheButtons.cs
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Media; using System.Windows.Controls; namespace Chapter12 { class DiagonalPanel : FrameworkElement { List<UIElement> children = new List<UIElement>(); Size sizeChildrenTotal; public static readonly DependencyProperty BackgroundProperty; // 静态构造函数创建依赖项属性Background static DiagonalPanel() { BackgroundProperty = DependencyProperty.Register("Background", typeof(Brush), typeof(DiagonalPanel), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); } public Brush Background { set { SetValue(BackgroundProperty, value); } get { return (Brush)GetValue(BackgroundProperty); } } // 添加孩子元素 public void Add(UIElement el) { children.Add(el); AddVisualChild(el); AddLogicalChild(el); InvalidateMeasure(); //更新元素布局 } public void Remove(UIElement el) { children.Remove(el); RemoveVisualChild(el); RemoveLogicalChild(el); InvalidateMeasure(); } public int IndexOf(UIElement el) { return children.IndexOf(el); } protected override int VisualChildrenCount { get { return children.Count; } } protected override Visual GetVisualChild(int index) { if (index >= children.Count) throw new ArgumentOutOfRangeException("index"); return children[index]; } protected override Size MeasureOverride(Size sizeAvailable) { sizeChildrenTotal = new Size(0, 0); foreach (UIElement child in children) { child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); sizeChildrenTotal.Width += child.DesiredSize.Width; sizeChildrenTotal.Height += child.DesiredSize.Height; } return sizeChildrenTotal; } protected override Size ArrangeOverride(Size sizeFinal) { Point ptChild = new Point(0, 0); foreach (UIElement child in children) { Size sizeChild = new Size(0, 0); sizeChild.Width = child.DesiredSize.Width * (sizeFinal.Width / sizeChildrenTotal.Width); sizeChild.Height = child.DesiredSize.Height * (sizeFinal.Height / sizeChildrenTotal.Height); child.Arrange(new Rect(ptChild, sizeChild)); // 当前元素的右下角位置为下一个元素的起点位置 ptChild.X += sizeChild.Width; ptChild.Y += sizeChild.Height; } return sizeFinal; } protected override void OnRender(DrawingContext dc) { dc.DrawRectangle(Background, null, new Rect(new Point(0, 0), RenderSize)); } } }
有了这个类,加入孩子的时候就可以直接写pal.Add(child)。
第2个代码文件:DiagonalPanel.cs
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; namespace Chapter12 { public class DiagonalizeTheButtons : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new DiagonalizeTheButtons()); } public DiagonalizeTheButtons() { Title = "对角线式排列按钮"; Width = 608; Height = 288; WindowStartupLocation = WindowStartupLocation.CenterScreen; DiagonalPanel pnl = new DiagonalPanel(); Content = pnl; Random rand = new Random(); for (int i = 0; i < 5; i++) { Button btn = new Button(); btn.Content = "按钮" +(i + 1); btn.FontSize += rand.Next(5); pnl.Add(btn); } } } }
运行效果:
第04个小程序:圆形排列按钮
一个圆可以被切割成多个扇形的小块。想将element排列成圆形,或许最直接的方式是让第个element都摆设在自己的小块内。
如下图所示,是一个小的扇形块,矩形表示按钮,从中心连到element右上角的这一条线,就是半径。
计算公式如下:
第01个代码文件:RadialPanelOrientation.cs
因为按钮通常都是宽(W)大于高(H),若按钮摆放方式不同(如旋转90度),这会影响半径的计算,不同的摆放方式会表现出来“以高度为主要决定因素”和“以宽度为主要决定因素”计算的半径。
using System; using System.Collections.Generic; using System.Text; namespace Chapter12 { public enum RadialPanelOrientation { ByWidth, ByHeight } }
第02个代码文件:RadialPanel.cs
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Media; using System.Windows.Controls; namespace Chapter12 { public class RadialPanel : Panel { public static readonly DependencyProperty OrientationProperty; bool showPieLines; double angleEach; // 角度 Size sizeLargest; // 最大孩子的尺寸 double radius; // 圆的半径 double outerEdgeFromCenter; double innerEdgeFromCenter; static RadialPanel() { OrientationProperty = DependencyProperty.Register("Orientation", typeof(RadialPanelOrientation), typeof(RadialPanel), new FrameworkPropertyMetadata(RadialPanelOrientation.ByWidth, FrameworkPropertyMetadataOptions.AffectsMeasure)); } public RadialPanelOrientation Orientation { set { SetValue(OrientationProperty, value); } get { return (RadialPanelOrientation)GetValue(OrientationProperty); } } public bool ShowPieLines { set { if (value != showPieLines) InvalidateVisual(); showPieLines = value; } get { return showPieLines; } } protected override Size MeasureOverride(Size sizeAvailable) { if (InternalChildren.Count == 0) return new Size(0, 0); angleEach = 360.0 / InternalChildren.Count; sizeLargest = new Size(0, 0); foreach (UIElement child in InternalChildren) { child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); sizeLargest.Width = Math.Max(sizeLargest.Width, child.DesiredSize.Width); sizeLargest.Height = Math.Max(sizeLargest.Height, child.DesiredSize.Height); } if (Orientation == RadialPanelOrientation.ByWidth) { // 计算中心到element边缘的距离 innerEdgeFromCenter = sizeLargest.Width / 2 / Math.Tan(Math.PI * angleEach / 360); outerEdgeFromCenter = innerEdgeFromCenter + sizeLargest.Height; // 以最大的孩子为基准,计算圆的半径 radius = Math.Sqrt(Math.Pow(sizeLargest.Width / 2, 2) + Math.Pow(outerEdgeFromCenter, 2)); } else { // 计算中心到element边缘的距离 innerEdgeFromCenter = sizeLargest.Height / 2 / Math.Tan(Math.PI * angleEach / 360); outerEdgeFromCenter = innerEdgeFromCenter + sizeLargest.Width; // 以最大的孩子为基准,计算圆的半径 radius = Math.Sqrt(Math.Pow(sizeLargest.Height / 2, 2) + Math.Pow(outerEdgeFromCenter, 2)); } // 返回该圆的尺寸 return new Size(2 * radius, 2 * radius); } protected override Size ArrangeOverride(Size sizeFinal) { double angleChild = 0; Point ptCenter = new Point(sizeFinal.Width / 2, sizeFinal.Height / 2); double multiplier = Math.Min(sizeFinal.Width / (2 * radius), sizeFinal.Height / (2 * radius)); foreach (UIElement child in InternalChildren) { // 重置孩子呈现位置的转换信息 child.RenderTransform = Transform.Identity; if (Orientation == RadialPanelOrientation.ByWidth) { // 将孩子放在上边 child.Arrange( new Rect(ptCenter.X - multiplier * sizeLargest.Width / 2, ptCenter.Y - multiplier * outerEdgeFromCenter, multiplier * sizeLargest.Width, multiplier * sizeLargest.Height)); } else { // 将孩子放在右边 child.Arrange( new Rect(ptCenter.X + multiplier * innerEdgeFromCenter, ptCenter.Y - multiplier * sizeLargest.Height / 2, multiplier * sizeLargest.Width, multiplier * sizeLargest.Height)); } // 旋转孩子 Point pt = TranslatePoint(ptCenter, child); child.RenderTransform = new RotateTransform(angleChild, pt.X, pt.Y); // 增加角度,准备安置下一个孩子 angleChild += angleEach; } return sizeFinal; } // 显示饼图切线 protected override void OnRender(DrawingContext dc) { base.OnRender(dc); if (ShowPieLines) { Point ptCenter = new Point(RenderSize.Width / 2, RenderSize.Height / 2); double multiplier = Math.Min(RenderSize.Width / (2 * radius), RenderSize.Height / (2 * radius)); Pen pen = new Pen(SystemColors.WindowTextBrush, 1); pen.DashStyle = DashStyles.Dash; // 显示圆 dc.DrawEllipse(null, pen, ptCenter, multiplier * radius, multiplier * radius); // 初始化角度 double angleChild = angleEach / 2; if (Orientation == RadialPanelOrientation.ByHeight) angleChild += 90; // 循环走过孩子,从中心绘制放射线 foreach (UIElement child in InternalChildren) { dc.DrawLine(pen, ptCenter, new Point(ptCenter.X + multiplier * radius * Math.Sin(2 * Math.PI * angleChild / 360), ptCenter.Y - multiplier * radius * Math.Cos(2 * Math.PI * angleChild / 360))); angleChild += angleEach; } } } } }
第03个代码文件:CircleTheButtons.cs
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace Chapter12 { public class CircleTheButtons : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new CircleTheButtons()); } RadialPanel pnl = new RadialPanel(); int btnCount; SolidColorBrush[] brushes = { Brushes.Red, Brushes.Green, Brushes.Blue, Brushes.Yellow,Brushes.Violet,Brushes.Orange, Brushes.PaleGreen,Brushes.RosyBrown,Brushes.Salmon,Brushes.Silver}; public CircleTheButtons() { Title = "圆形排列按钮"; WindowStartupLocation = WindowStartupLocation.CenterScreen; Width = 768; Height = 432; Topmost = true; SizeChanged += OnSizeChanged; Canvas canv = new Canvas(); pnl.Width = Width - 8; pnl.Height = Height - 34; pnl.Orientation = RadialPanelOrientation.ByWidth; pnl.ShowPieLines = true; canv.Children.Add(pnl); // 互动按钮 Button btnIsShowPie = new Button(); btnIsShowPie.Width = 120; btnIsShowPie.Content = "不显示饼图"; btnIsShowPie.Click += IsShowPieClick; canv.Children.Add(btnIsShowPie); Canvas.SetLeft(btnIsShowPie, 10); Canvas.SetTop(btnIsShowPie, 10); Button btnRadialPanelOrientation = new Button(); btnRadialPanelOrientation.Width = 120; btnRadialPanelOrientation.Content = "以高度为主排列"; btnRadialPanelOrientation.Click += RadialPanelOrientationClick; canv.Children.Add(btnRadialPanelOrientation); Canvas.SetLeft(btnRadialPanelOrientation, 10); Canvas.SetTop(btnRadialPanelOrientation, 50); Button btnAddButton = new Button(); btnAddButton.Width = 120; btnAddButton.Content = "增加按钮"; btnAddButton.Click += AddButtonClick; canv.Children.Add(btnAddButton); Canvas.SetLeft(btnAddButton, 10); Canvas.SetTop(btnAddButton, 90); Button btnCutButton = new Button(); btnCutButton.Width = 120; btnCutButton.Content = "减少按钮"; btnCutButton.Click += CutButtonClick; canv.Children.Add(btnCutButton); Canvas.SetLeft(btnCutButton, 10); Canvas.SetTop(btnCutButton, 130); Content = canv; Random rand = new Random(); int i; for (i = 0; i < 9; i++) { Button btn = new Button(); btn.Content = "按钮" + (i + 1); btn.Background = brushes[i]; btn.FontSize += rand.Next(10); pnl.Children.Add(btn); } btnCount = i; } // 响应事件 void OnSizeChanged(object sender, EventArgs e) { pnl.Width = this.Width - 8; pnl.Height = Height - 34; pnl.InvalidateMeasure(); } void IsShowPieClick(object sender, RoutedEventArgs e) { Button btn = e.Source as Button; if (pnl.ShowPieLines == true) { pnl.ShowPieLines = false; btn.Content = "显示饼图"; } else { pnl.ShowPieLines = true; btn.Content = "不显示饼图"; } } void RadialPanelOrientationClick(object sender, RoutedEventArgs e) { Button btn = e.Source as Button; if (pnl.Orientation == RadialPanelOrientation.ByWidth) { pnl.Orientation = RadialPanelOrientation.ByHeight; btn.Content = "以宽度为主排列"; pnl.InvalidateVisual(); } else { pnl.Orientation = RadialPanelOrientation.ByWidth; btn.Content = "以高度为主排列"; pnl.InvalidateVisual(); } } void AddButtonClick(object sender, RoutedEventArgs e) { if (btnCount < 30) { Random rand = new Random(); int i = rand.Next(brushes.Length - 1); Button btn = new Button(); btn.Content = "按钮" + (btnCount + 1); btn.Background = brushes[i]; btn.FontSize += rand.Next(10); pnl.Children.Add(btn); btnCount++; pnl.InvalidateVisual(); } } void CutButtonClick(object sender, RoutedEventArgs e) { if (btnCount > 4) { UIElementCollection uiEleColl = pnl.Children; uiEleColl.RemoveAt(btnCount - 1); btnCount--; } pnl.InvalidateVisual(); } } }
运行效果如下:
相关文章推荐
- (WPF学习记录)第十章 自定义Element
- 【WPF】制作自定义的列表项面板
- 记录进阶之路——自定义view学习一
- WPF 学习记录(乱七八糟的小问题)
- 【Swift学习笔记-《PRODUCT》读书记录-实现自定义转场动画】
- 【ios学习记录】- 通过nib文件实现自定义表视图单元
- WPF学习总结和记录(八)-尺寸缩放 定位 下
- Oracle学习记录之使用自定义函数和触发器实现主键动态生成
- WPF:从WPF Diagram Designer Part 2学习面板、缩略图、框线选择和工具箱-下
- WPF学习记录2_XAML
- iOS学习爬坑记录8:关于自定义Cell的一点认识
- WPF学习 第十二章2 画刷
- WPF 内建面板学习总结(一)
- WPF 学习记录——Basic Brushes
- Unity里Inspector面板自定义学习——Custom Data,序列化一个类,自定义 property drawer
- WPF入门教程系列一——基础 一、 前言 最近在学习WPF,学习WPF首先上的是微软的MSDN,然后再搜索了一下网络有关WPF的学习资料。为了温故而知新把学习过程记录下来,以备后
- 自定义WPF布局面板
- (WPF学习记录)第七章 Canvas
- [EntLib]微软企业库5.0 学习之路——第九步、使用PolicyInjection模块进行AOP—PART4——建立自定义Call Handler实现用户操作日志记录
- 流畅的python第十二章继承的优缺点学习记录