完善基于WPF开发的标尺控件(含实例代码)
引言
在WPF应用程序开发中,自定义控件是提升用户体验和界面美观度的重要手段。标尺控件作为测量和定位工具,在图像编辑、文档处理等场景中具有广泛应用。本文将详细介绍如何完善一个基于WPF的自定义标尺控件,涵盖设计思路、实现方法和完整代码示例。
一、设计思路
1.1 核心需求分析
完善的标尺控件应具备以下特性:
支持水平和垂直方向显示
可自定义刻度间隔和单位
支持缩放功能
可显示当前测量值
支持DPI适配
良好的性能表现(支持大量刻度)
1.2 架构设计
采用MVVM模式分离视图和逻辑:
视图层:XAML实现标尺的视觉表现
视图模型层:处理刻度计算、缩放逻辑等
模型层:定义刻度数据结构和单位转换
二、实现方法
2.1 创建自定义控件
首先创建一个继承自Control的自定义控件:
csharp
Copy Code
public class RulerControl : Control
{
// 定义依赖属性
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(RulerControl),
new PropertyMetadata(Orientation.Horizontal));
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
// 其他依赖属性定义...
}
2.2 刻度数据模型
定义刻度数据模型,包含数值和可见性属性:
csharp
Copy Code
public class RulerChip
{
public double Number { get; set; }
public Visibility NumberVisibility { get; set; }
}
2.3 刻度生成逻辑
在视图模型中实现刻度生成算法:
csharp
Copy Code
public class RulerViewModel
{
private double _scale = 1.0;
private double _unit = 1.0; // 1表示像素,0.1表示厘米等
public List<RulerChip> Chips { get; } = new List<RulerChip>();
public double Scale
{
get => _scale;
set
{
if (_scale != value)
{
_scale = value;
GenerateChips();
OnPropertyChanged(nameof(Scale));
}
}
}
public double Unit
{
get => _unit;
set
{
if (_unit != value)
{
_unit = value;
GenerateChips();
OnPropertyChanged(nameof(Unit));
}
}
}
private void GenerateChips()
{
Chips.Clear();
// 根据缩放比例和单位计算刻度
double interval = 10 * _unit; // 主刻度间隔
double subInterval = 1 * _unit; // 次刻度间隔
for (double i = 0; i < 1000; i += subInterval)
{
bool isMain = (i % interval == 0);
Chips.Add(new RulerChip
{
Number = i / _unit,
NumberVisibility = isMain ? Visibility.Visible : Visibility.Hidden
});
}
}
}
2.4 缩放功能实现
添加缩放滑块控件和事件处理:
csharp
Copy Code
public class RulerControl : Control
{
// ... 其他依赖属性
public event EventHandler ScaleChanged;
public double ScaleFactor
{
get { return (double)GetValue(ScaleFactorProperty); }
set { SetValue(ScaleFactorProperty, value); }
}
public static readonly DependencyProperty ScaleFactorProperty =
DependencyProperty.Register("ScaleFactor", typeof(double), typeof(RulerControl),
new PropertyMetadata(1.0, ScaleFactorChanged));
private static void ScaleFactorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((RulerControl)d).ScaleChanged?.Invoke(d, EventArgs.Empty);
}
}
2.5 DPI适配
实现DPI感知功能,确保在不同DPI设置下显示一致:
csharp
Copy Code
public class RulerControl : Control
{
public static readonly DependencyProperty DpiProperty =
DependencyProperty.Register("Dpi", typeof(double), typeof(RulerControl),
new PropertyMetadata(96.0)); // 默认96dpi
public double Dpi
{
get { return (double)GetValue(DpiProperty); }
set { SetValue(DpiProperty, value); }
}
protected override void OnRender(DrawingContext drawingContext)
{
// 根据DPI调整绘制参数
double dpiScale = Dpi / 96.0;
double scaledWidth = ActualWidth * dpiScale;
double scaledHeight = ActualHeight * dpiScale;
// 绘制刻度...
}
}
三、完整代码示例
3.1 XAML实现
xml
Copy Code
<UserControl x:Class="WpfRulerDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfRulerDemo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 缩放滑块 -->
<Slider x:Name="ScaleSlider" Grid.Row="0" Minimum="0.1" Maximum="10" Value="1"
ValueChanged="ScaleSlider_ValueChanged"/>
<!-- 水平标尺 -->
<local:RulerControl x:Name="HorizontalRuler" Grid.Row="1" Orientation="Horizontal"
ScaleFactor="{Binding ScaleFactor, ElementName=ScaleSlider}"
Unit="0.1" Dpi="96"/>
<!-- 垂直标尺 -->
<local:RulerControl x:Name="VerticalRuler" Grid.Row="1" Orientation="Vertical"
ScaleFactor="{Binding ScaleFactor, ElementName=ScaleSlider}"
Unit="0.1" Dpi="96"/>
</Grid>
</UserControl>
3.2 视图模型
csharp
Copy Code
public class MainWindowViewModel : INotifyPropertyChanged
{
private double _scaleFactor = 1.0;
public double ScaleFactor
{
get => _scaleFactor;
set
{
if (_scaleFactor != value)
{
_scaleFactor = value;
OnPropertyChanged(nameof(ScaleFactor));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
3.3 刻度绘制逻辑
csharp
Copy Code
public class RulerControl : Control
{
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (Orientation == Orientation.Horizontal)
{
DrawHorizontalRuler(drawingContext);
}
else
{
DrawVerticalRuler(drawingContext);
}
}
private void DrawHorizontalRuler(DrawingContext drawingContext)
{
double dpiScale = Dpi / 96.0;
double scaledWidth = ActualWidth * dpiScale;
// 绘制刻度线...
for (int i = 0; i < scaledWidth; i += 10 * ScaleFactor * dpiScale)
{
double x = i;
double y = 0;
// 主刻度
if (i % (50 * ScaleFactor * dpiScale) == 0)
{
drawingContext.DrawLine(new Pen(Brushes.Black, 2 * ScaleFactor * dpiScale),
new Point(x, y), new Point(x, 10 * ScaleFactor * dpiScale));
// 绘制刻度值
FormattedText text = new FormattedText(
(i / dpiScale / ScaleFactor).ToString("0.0"),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
8 * ScaleFactor * dpiScale,
Brushes.Black);
drawingContext.DrawText(text, new Point(x - text.Width / 2, y + 15 * ScaleFactor * dpiScale));
}
// 次刻度
else if (i % (10 * ScaleFactor * dpiScale) == 0)
{
drawingContext.DrawLine(new Pen(Brushes.Gray, ScaleFactor * dpiScale),
new Point(x, y), new Point(x, 5 * ScaleFactor * dpiScale));
}
}
}
// 垂直标尺绘制类似...
}
四、性能优化
4.1 虚拟化技术
对于大量刻度,使用VirtualizingStackPanel提高性能:
xml
Copy Code
<ListBox x:Name="RulerListBox" VirtualizingStackPanel.IsVirtualizing="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Rectangle Width="2" Height="10" Fill="{Binding NumberVisibility, Converter={StaticResource VisibilityToBrushConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
4.2 数据绑定优化
使用INotifyPropertyChanged实现高效数据绑定:
csharp
Copy Code
public class RulerChip : INotifyPropertyChanged
{
private double _number;
private Visibility _numberVisibility;
public double Number
{
get => _number;
set
{
if (_number != value)
{
_number = value;
OnPropertyChanged(nameof(Number));
}
}
}
// 其他属性...
}
五、总结
本文详细介绍了如何完善一个基于WPF的自定义标尺控件,从设计思路到具体实现,提供了完整的代码示例和性能优化方案。通过使用WPF的数据绑定、依赖属性和自定义绘制等技术,可以创建出功能丰富、性能优良的自定义控件。实际应用中,可以根据需求进一步扩展功能,如添加测量工具、支持更多单位等。
下一篇 >>