wpf 写个简单的控件吧
今天看到了老赵的一片博客:编写一个“绑定友好”的WPF控件
文章里面遇到的问题,蛮有意思,后面的评论,非常精彩,没看过的,推荐看一下。
由于我也做过类似的需求,所以,贴出我当时的做法和现在的想法,仅仅是笔记,没有其他意思。
当时的需求类似这样子,实际做的效果当然比这个漂亮的多。
看到这个UI的第一反应就是,封装一个控件,把slider包进去,很简单的吧。
当时的做法,在CS代码里面封装几个DP,绑定就完事儿了呀。
<UserControl x:Class="WpfControlTest.UCSlider" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="250" Height="45" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Name="main" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" > <Grid> <TextBlock Text="{Binding ElementName=main,Path=Text}" Margin="0,0,0,0" Foreground="Black" VerticalAlignment="Top" HorizontalAlignment="Left"/> <TextBlock Text="{Binding ElementName=sl,Path=Value,StringFormat=0}" Foreground="Black" HorizontalAlignment="Right"/> <Slider Name="sl" Width="{Binding Path=Width,ElementName=main}" VerticalAlignment="Bottom" Cursor="Hand" Maximum="{Binding ElementName=main,Path=Maximum}" Minimum="{Binding ElementName=main,Path=Minimum}" Value="{Binding ElementName=main,Path=SliderValue}" /> </Grid> </UserControl>
跟老赵那个类似,实际的使用场景也就是这么回事,cs代码当时根本就没想到用MVVM,基本没有逻辑,没必要分离。
只是需要绑定,所以DP少不了的。比较简单,要细看的自己展开。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// when value changed,use eventhandle to notify 3 /// </summary> 4 /// <param name="value"></param> 5 public delegate void CustomSliderEventHandle(double value); 6 7 public partial class UCSlider : UserControl 8 { 9 public UCSlider() 10 { 11 InitializeComponent(); 12 sl.ValueChanged += new RoutedPropertyChangedEventHandler<double>(sl_ValueChanged); 13 } 14 15 #region event 16 public event CustomSliderEventHandle ValueChange; 17 18 void sl_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) 19 { 20 if (ValueChange != null) 21 ValueChange(sl.Value); 22 } 23 #endregion 24 25 #region Text 26 public string Text 27 { 28 get { return (string)GetValue(TextProperty); } 29 set { SetValue(TextProperty, value); } 30 } 31 32 public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UCSlider)); 33 #endregion 34 35 #region Maximum 36 public double Maximum 37 { 38 get { return (double)GetValue(MaximumProperty); } 39 set { SetValue(MaximumProperty, value); } 40 } 41 42 public static DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(UCSlider)); 43 #endregion 44 45 #region Minimum 46 public double Minimum 47 { 48 get { return (double)GetValue(MinimumProperty); } 49 set { SetValue(MinimumProperty, value); } 50 } 51 52 public static DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(UCSlider)); 53 #endregion 54 55 #region SliderValue 56 public double SliderValue 57 { 58 get { return (double)GetValue(SliderValueProperty); } 59 set { SetValue(SliderValueProperty, value); } 60 } 61 62 public static DependencyProperty SliderValueProperty = DependencyProperty.Register("SliderValue", typeof(double), typeof(UCSlider)); 63 #endregion 64 }
没有用MVVM,所以比老赵那个简单了不少。
到了调用的地方,貌似是搞个ViewModel的时候,其实只是用到了NotifyPropertyChanged,因为这个玩起来爽啊。
UI:
<StackPanel HorizontalAlignment="Left" Margin="10,10,0,0" > <UC:UCSlider Text="Brightness" Maximum="100" Minimum="0" SliderValue="{Binding Path=Brightness,Mode=TwoWay}" > </UC:UCSlider> <UC:UCSlider Text="Contrast" Maximum="100" Minimum="0" SliderValue="{Binding Path=Contrast,Mode=TwoWay}"/> <UC:UCSlider Text="Hue" Maximum="2" Minimum="-2" SliderValue="{Binding Path=Hue,Mode=TwoWay}"/> </StackPanel>
CS:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// Interaction logic for MainWindow.xaml 3 /// </summary> 4 public partial class MainWindow : Window 5 { 6 TurningViewModel _turningViewModel = new TurningViewModel() { Hue = 1, Brightness = 20, Contrast = 2 }; 7 public MainWindow() 8 { 9 InitializeComponent(); 10 this.DataContext = _turningViewModel; 11 _turningViewModel.PropertyChanged += new PropertyChangedEventHandler(_turningViewModel_PropertyChanged); 12 } 13 14 void _turningViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 15 { 16 Debug.WriteLine(string.Format("PropertyName:{0}", e.PropertyName)); 17 } 18 19 private void get_Click(object sender, RoutedEventArgs e) 20 { 21 Debug.WriteLine(string.Format("Hue:{0}", _turningViewModel.Hue)); 22 Debug.WriteLine(string.Format("Brightness:{0}", _turningViewModel.Brightness)); 23 Debug.WriteLine(string.Format("Contrast:{0}", _turningViewModel.Contrast)); 24 } 25 } 26 27 public class TurningViewModel : INotifyPropertyChanged 28 { 29 #region Hue 30 private double _hue; 31 public double Hue 32 { 33 get { return _hue; } 34 set 35 { 36 if (_hue != value) 37 { 38 _hue = value; 39 NotifyPropertyChanged("Hue"); 40 } 41 } 42 } 43 #endregion 44 45 #region Brightness 46 private double _brightness; 47 public double Brightness 48 { 49 get { return _brightness; } 50 set 51 { 52 if (_brightness != value) 53 { 54 _brightness = value; 55 NotifyPropertyChanged("Brightness"); 56 } 57 } 58 } 59 #endregion 60 61 #region Contrast 62 private double _contrast; 63 public double Contrast 64 { 65 get { return _contrast; } 66 set 67 { 68 if (_contrast != value) 69 { 70 _contrast = value; 71 NotifyPropertyChanged("Contrast"); 72 } 73 } 74 } 75 #endregion 76 77 void NotifyPropertyChanged(string propertyName) 78 { 79 if (PropertyChanged != null) 80 { 81 PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 82 } 83 } 84 85 public event PropertyChangedEventHandler PropertyChanged; 86 }
基本上就结束了呀。
如果要把这个控件做成自定义控件,继承自Control的,
XAML:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <Style TargetType="{x:Type local:CCSlider}"> 2 <Setter Property="Template"> 3 <Setter.Value> 4 <ControlTemplate TargetType="{x:Type local:CCSlider}"> 5 <Border Background="{TemplateBinding Background}" 6 BorderBrush="{TemplateBinding BorderBrush}" 7 BorderThickness="{TemplateBinding BorderThickness}"> 8 <Grid Width="200" Height="45"> 9 <TextBlock Text="{TemplateBinding Text}" Margin="0,0,0,0" Foreground="Black" VerticalAlignment="Top" HorizontalAlignment="Left"/> 10 <TextBlock Text="{Binding ElementName=sl,Path=Value,StringFormat=0}" Foreground="Black" HorizontalAlignment="Right"/> 11 <Slider Name="sl" Width="{Binding Path=Width,ElementName=main}" VerticalAlignment="Bottom" Cursor="Hand" 12 Maximum="{TemplateBinding Maximum}" 13 Minimum="{TemplateBinding Minimum}" 14 Value="{Binding Path=SliderValue,Mode=TwoWay,RelativeSource={RelativeSource Mode=TemplatedParent}}" /> 15 </Grid> 16 </Border> 17 </ControlTemplate> 18 </Setter.Value> 19 </Setter> 20 </Style>
CS 代码和 上面那个一样的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public class CCSlider : Control 2 { 3 static CCSlider() 4 { 5 DefaultStyleKeyProperty.OverrideMetadata(typeof(CCSlider), new FrameworkPropertyMetadata(typeof(CCSlider))); 6 } 7 8 9 #region Text 10 public string Text 11 { 12 get { return (string)GetValue(TextProperty); } 13 set { SetValue(TextProperty, value); } 14 } 15 16 public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CCSlider)); 17 #endregion 18 19 #region Maximum 20 public double Maximum 21 { 22 get { return (double)GetValue(MaximumProperty); } 23 set { SetValue(MaximumProperty, value); } 24 } 25 26 public static DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(CCSlider)); 27 #endregion 28 29 #region Minimum 30 public double Minimum 31 { 32 get { return (double)GetValue(MinimumProperty); } 33 set { SetValue(MinimumProperty, value); } 34 } 35 36 public static DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(CCSlider)); 37 #endregion 38 39 #region SliderValue 40 public double SliderValue 41 { 42 get { return (double)GetValue(SliderValueProperty); } 43 set { SetValue(SliderValueProperty, value); } 44 } 45 46 public static DependencyProperty SliderValueProperty = DependencyProperty.Register("SliderValue", typeof(double), typeof(CCSlider)); 47 #endregion 48 }
顺便唠叨:
1. 控件自定义有两种:
一种是和逻辑相关的,里面可以搞搞ViewModel,分离一下。如果分离,DataContext必须指明,要不然和老赵的那个错误类似了。如果逻辑不复杂,就没那个必要了,直接balala一搞就完事儿了。
如果业务粒度分的细,逻辑比较单纯的,还是不要费那个事儿了,毕竟ViewModel的代码量大啊,我承认自己懒。
这个ASP.net的模板页面套子页面一个道理。
这一种用UserControl搞搞,方便,感觉就是为了分割逻辑用的。
另外一种是和逻辑无关的,例如三态按钮,要大量复用,或者要做类似RadioButton的效果,只有一个被选中。
那就继承自Control吧。
这个区分方法也不是严格定义的,看实际需求吧。
2.再来说MVVM,关于这个东东的争论貌似也不少,快赶上 JAVA和C#的争论了。
不少的WPF开发者,潜意识里面,随便写个东东都MVVM,不管大大小小都搞个ViewModel。
有时候.xaml.cs里面几乎啥都没放,ViewModel的代码倒是急剧膨胀,何苦啊。
MVVM的理论解释,还是有微软的说法,等等,都在MSDN里面,不再贴出来了。
只说说自己的体会:
刚开始搞WPF的时候,从ASP.net转过来的,WinForm也玩过,习惯了代码搞定一切。
于是乎,套用,几乎一切都是代码搞定,因为这个相对熟悉,而且调试方便啊。
后来项目凑合着搞完了,开始静下心来看看WPF,看看MVVM,看看绑定啊等等东西。|
也做了Demo,发现如果熟悉XAML,挺好,一句可以代替CS代码好多句,清爽,简练,不熟悉,苦逼的事情很多。
MVVM,第一次用是在 数据的增删改,新增家具,要填写一堆的属性,家具列表,修改家具...
按照之前的做法,写个类,一个一个属性的绑定,保存的时候,一个一个的获取...,你懂的,十几个属性,会死人的。
用MVVM,只要在XAML里面绑定数据,UI逻辑需要访问控件的,在.xaml.cs中写一下,数据加工逻辑不会涉及控件的都在ViewModel中,获取数据的时候直接取ViewModel。
这是我发现的最最适用的场合。
我想到最初做Winform的时候,有个叫DataGrid(名字不太确定)的控件,直接可以编辑表格内容,排序,增删改都集成了。
当时是医药销售行业,老大们把业务逻辑封装在存储过程中,UI就直接拿DataGrid,拖过来,给个数据源,保存的时候取一下数据,完事儿了。
并且卖钱了,还不少,这就是MS最牛叉的地方,直接用,直接卖钱。
数据增删改,是直接产生效益的地方,一般的销售类软件都需要这个功能,微软貌似就在这里下功夫,Asp.net 和Winfor都是,WPF的MVVM也是。
再回头说说做的第一个项目,后来准备重构,弄个MVVM上去,发现不爽。
这个项目类似涂鸦墙,文字、图片、Flash等等可以随意的放在画布上,而且可以移动、旋转、缩放。
原因是数据加工逻辑基本没有,就是保存,提取。逻辑集中在UI上,最要命的是移动、旋转、缩放,而且还组合操作。
如果我用MVVM,倒也可以,但是还是用CS代码控制比较爽。
说说现在的看法:
举个例子,ListView,数据加载\加工的逻辑在ViewModel,双击列头排序和双击条目跳转这种逻辑在.xaml.cs中。
再举个例子,带有复选框文件树,用TreeView实现的,勾选子结点,父节点被勾选还是咋的,纯粹是数据层面的事情,那就在ViewModel中实现。
某个节点被选中了,要获取当前的选中项数据,在Xaml.cs中显然方便一点,在ViewModel中检测PropertyChange当然也可以。
数据相关的在ViewModel中,需要访问控件的在.xaml.cs中,当然了,有时候也可以做一下均衡。
只有后者的,不实现MVVM。
再扯远点,就是数据驱动开发了。
其实就一句话:根据实际业务情况决定和重构。