5 突破底线,穷根究底
5.1 本例用到了 依赖性属性 自定义控件 值转换器 还有一些C#委托和事件 继承相关的基础知识,让你更进一步了解Command,自己写一个拓展了 Slider 控件属性(Command相关的)的一个新的Slider控件
先看前台代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
<Window x:Class="Commands.CustomControlWithCommand"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Commands"
Title="CustomControlWithCommand" Height="500" Width="400"
>
<Window.Resources>
<local:FontStringValueConverter x:Key="StringConverterResource"/>
<local:FontDoubleValueConverter x:Key="DoubleConverterResource"/>
</Window.Resources>
<StackPanel>
<Border BorderBrush="Black"
BorderThickness="2"
Margin="10"
Width="400"
Height="400">
<StackPanel>
<StackPanel Margin="10">
<Label HorizontalAlignment="Center">
Custom Slider that Invokes a Command
</Label>
<Border Width="350" Background="LightBlue">
<local:CommandSlider x:Name="FontSlider"
Background="AliceBlue"
Minimum="0"
Maximum="60"
Value="12"
TickFrequency="5"
Height="50"
Width="275"
TickPlacement="BottomRight"
LargeChange="5"
SmallChange="5"
AutoToolTipPlacement="BottomRight"
AutoToolTipPrecision="0"
Command="{x:Static local:CustomControlWithCommand.FontUpdateCommand}"
CommandTarget="{Binding ElementName=txtBoxTarget}"
CommandParameter="{Binding ElementName=FontSlider,
Path=Value,
Converter={StaticResource DoubleConverterResource}}"
Focusable="False"/>
</Border>
</StackPanel>
<Border BorderBrush="Black"
BorderThickness="1"
Height="150"
Width="300"
Margin="15">
<StackPanel Margin="5">
<CheckBox IsChecked="False"
Checked="OnReadOnlyChecked"
Unchecked="OnReadOnlyUnChecked"
Content="Read Only"
Margin="5"
FontSize="12" />
<TextBox Name="txtBoxTarget" Height="100" Width="275" Margin="3">
<TextBox.CommandBindings>
<CommandBinding Command="{x:Static local:CustomControlWithCommand.FontUpdateCommand}"
Executed="SliderUpdateExecuted"
CanExecute="SliderUpdateCanExecute" />
</TextBox.CommandBindings>
Hello
</TextBox>
</StackPanel>
</Border>
<StackPanel>
<Label HorizontalAlignment="Center">
More Command Sources for the Font Update Command
</Label>
<StackPanel Margin="10" HorizontalAlignment="Left" Background="LightBlue">
<Button Command="{x:Static local:CustomControlWithCommand.FontUpdateCommand}"
CommandTarget="{Binding ElementName=txtBoxTarget}"
CommandParameter="{Binding ElementName=txtValue,
Path=Text,
Converter={
StaticResource StringConverterResource}}"
Height="50"
Width="150"
Margin="1">
Update Font Via Command
</Button>
<TextBox Name="txtValue"
MaxLength="2"
Width="25"
Background="AliceBlue"
Margin="0,0,0,3">5</TextBox>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</Window>
后台代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Globalization;
namespace Commands
{
/// <summary>
/// Interaction logic for CustomControlWithCommand.xaml
/// </summary>
public partial class CustomControlWithCommand : System.Windows.Window
{
public CustomControlWithCommand()
{
InitializeComponent();
}
public static RoutedCommand FontUpdateCommand = new RoutedCommand();
//The ExecutedRoutedEvent Handler
//if the command target is the TextBox, changes the fontsize to that
//of the value passed in through the Command Parameter
public void SliderUpdateExecuted(object sender, ExecutedRoutedEventArgs e)
{
TextBox source = sender as TextBox;
if (source != null)
{
if (e.Parameter != null)
{
try
{
if ((int)e.Parameter > 0 && (int)e.Parameter <= 60)
{
source.FontSize = (int)e.Parameter;
}
}
catch
{
MessageBox.Show("in Command \n Parameter: " + e.Parameter);
}
}
}
}
//The CanExecuteRoutedEvent Handler
//if the Command Source is a TextBox, then set CanExecute to ture;
//otherwise, set it to false
public void SliderUpdateCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
TextBox source = sender as TextBox;
if (source != null)
{
if (source.IsReadOnly)
{
e.CanExecute = false;
}
else
{
e.CanExecute = true;
}
}
}
//if the Readonly Box is checked, we need to force the CommandManager
//to raise the RequerySuggested event. This will cause the Command Source
//to query the command to see if it can execute or not.
public void OnReadOnlyChecked(object sender, RoutedEventArgs e)
{
if (txtBoxTarget != null)
{
txtBoxTarget.IsReadOnly = true;
CommandManager.InvalidateRequerySuggested();
}
}
//if the Readonly Box is checked, we need to force the CommandManager
//to raise the RequerySuggested event. This will cause the Command Source
//to query the command to see if it can execute or not.
public void OnReadOnlyUnChecked(object sender, RoutedEventArgs e)
{
if (txtBoxTarget != null)
{
txtBoxTarget.IsReadOnly = false;
CommandManager.InvalidateRequerySuggested();
}
}
}
//Converter to convert the Slider value property to an int
[ValueConversion(typeof(double), typeof(int))]
public class FontStringValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string fontSize = (string)value;
int intFont;
try
{
intFont = Int32.Parse(fontSize);
return intFont;
}
catch (FormatException e)
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
//Converter to convert the Slider value property to an int
[ValueConversion(typeof(double), typeof(int))]
public class FontDoubleValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double fontSize = (double)value;
return (int)fontSize;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
CommandSlider类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
看了这么多代码,你可能要疯了,不过不要急,好多都是一个原理
5.2 潜移默化
讲解:
先讲CommandSlider类,首先他继承了 Slider 类,实现了 ICommandSource 接口,目的很明显了,就是拓展Slider控件,它本身就是一个Slider控件了,这点很重要! 那个接口只是为了能让调用者能调用Command相关的依赖性属性
废话不多说,正题:
导入命名控件 System.Windows.Input 和 System.Windows.Controls
(快捷键 鼠标指在ICommand上 按Shift+Alt+F10 快速提示命名空间的一个快捷键)
public CommandSlider()
: base()
{ }
就是继承父类所有的东西,这个有关 C# OOP中的继承 在这里我就不多说了,没有学过 OOP 的还是尽量不要看了
学过依赖性属性的都知道 依赖性属性怎么定义的了
第一步:声明一个属性
例子:
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
其中 CommandProperty是依赖性属性定义的规范,要在定义的属性名后面加Property,本例是Command,ICommand类型的,所以后面加上Property就变成了CommandProperty; 这里的get和set也特殊,也只能这样写GetValue等什么的,如果没注册 CommandProperty会出错,没关系放在那里,接下来注册
第二步:注册依赖性属性
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(CommandSlider),
new PropertyMetadata(
(ICommand)null, new PropertyChangedCallback(CommandChanged))
);
这里的CommandProperty 就是那个对应的名字,Register( 那个属性名字, 它对应的类型, 本类的类名(一般都这样),其他处理),可以这样理解 PropertyChangedCallback 回调方法, CommandChanged 是一个方法的名称,看词义就知道是 Command的值改变时调用的,前面那个
(ICommand)null 是默认值,表示该Command是null
同理,注册 CommandTarget和CommandParameter依赖性属性 备注:CommandTarget 这个命令所影响的对象 CommandParameter 是执行命令添加的一些参数
//make CommandTarget a dependency property so it can be DataBound
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register(
"CommandTarget",
typeof(IInputElement),
typeof(CommandSlider),
new PropertyMetadata((IInputElement)null));
public IInputElement CommandTarget
{
get
{
return (IInputElement)GetValue(CommandTargetProperty);
}
set
{
SetValue(CommandTargetProperty, value);
}
}
//make CommandParameter a dependency property so it can be DataBound
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(CommandSlider),
new PropertyMetadata((object)null));
public object CommandParameter
{
get
{
return (object)GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
第三步:其他步骤了,就按本例子去说吧
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
// Command dependency property change callback
private static void CommandChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
CommandSlider cs = (CommandSlider)d;
cs.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
// Add a new command to the Command Property
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
//if oldCommand is not null, then we need to remove the handlers
if (oldCommand != null)
{
RemoveCommand(oldCommand, newCommand);
}
AddCommand(oldCommand, newCommand);
}
// Remove an old command from the Command Property
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
// add the command
private void AddCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.Command != null)
{
RoutedCommand command = this.Command as RoutedCommand;
// if RoutedCommand
if (command != null)
{
if (command.CanExecute(CommandParameter, CommandTarget))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
// if not RoutedCommand
else
{
if (Command.CanExecute(CommandParameter))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
}
CommandChanged事件就是上面Command属性改变时触发的事件
首先获得触发事件的事件源 这里肯定知道是CommandSlider了 所以CommandSlider cs = (CommandSlider)d; 例如 假如有N个 CommandSlider控件 就有N个Command属性,你不知道是哪个触发的,就可以这样干,这一个技巧非常经常用到,通过e.OldValue,e.NewValue获得改变前的值和改变后的值,你懂的,不说了
HookUpCommand ,hookup是连接的意思,先不看 RemoveCommand(移除Command属性) 和AddCommand(添加Command属性) 这两个方法
接下来我们来看CanExecuteChanged方法
因为Command属性是ICommand类型的,RoutedCommand实现了ICommand ,所以Command 可以转换为 RoutedCommand(路由命令)类型的,转换成功就可以处理了
如果是路由命令,通过command.CanExecute(2个参数)方法判断 此路由命令在当前状态下是否可行,可行就设为将控件状态设为可用(true),不可行设为不可用
如果不是路由命令,通过command.CanExecute(1个参数)方法判断 此路由命令在当前状态下是否可行,可行就设为将控件状态设为可用(true),不可行设为不可用
接下来讲解:
RemoveCommand(移除Command属性) 和AddCommand(添加Command属性) 这两个方法,还有HookUpCommand方法
思路:改变值是有2个值,一个是原本的值,一个是新值 ,原值不为空,就先移除原值上的CanExecuteChanged事件,这样的话当老值改变的时候就不会调用他的CanExecuteChanged事件了,就不会影响到新值了。
接下来就可以在 新值上绑定 CanExecuteChanged 事件,
插一句 EventHandler handler = new EventHandler(CanExecuteChanged); 等同于 EventHandler handler = CanExecuteChanged; 学委托和事件的时候应该都知道,这是基本的 ,这里是在EventHandler上绑定一个CanExecuteChanged事件
为了避免GC(垃圾收集器)回收了,我们创建一个副本 private static EventHandler canExecuteChangedHandler;
canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
在新值上绑定个CanExecuteChanged事件;
还有一个重写父类的OnValueChanged的方法,就是在原来的Slider控件的这个事件中拓展了一下,这个方法干嘛的呢?
就是如果 Command是定义好的,那么移动滑条的时候,这个控件(Slider,现在是CommandSlider)就会调用(invoke)这个Command,否则的话这个滑条就是个很普通的滑条,因为看他的参数就知道他没有绑定 受影响的对象。发现了规律:RoutedCommand(路由命令)的Execute有两个参数,包含了CommandTarget,而ICommand的Executed没有CommandTarget,这样区分了,下面我来俗语话讲解 路由命令 来理解 路由 这个抽象概念 就是为什么路由命令才能有效果。
路由命令: 假设 A 是 Slider控件 B代表TextBox控件 我现在想通过Slider的滑条改变TextBox中的字体大小的值, 有很多方式实现,这里用命令实现 现在把 ”字体变大“ 这个信号叫做要路由的东西C。 路由是父类,命令路由是子类,因为路由有很多种,比如事件路由等
很明显,现在主要母的是要通过 滑条 控制 文本框 的变化,2个对象,可以这样读,A把C路由到了B上 在具体一点 A把C通过 “命令路由”这类路由 影响到了B上,然后B受到影响,字体变大了,现在懂了吧,我不知道对不对,专业的请谅解一下,呵呵,谢谢了
到此为止应该完成了CommandSlider的讲解了,主要目的 是 我们手动给一个没有Command相关的依赖性属性的控件 添加Command相关的依赖性属性,到此为止,我们的Slider升级版的CommandSlider已经完成了,接下来就是用了
在后台代码中...
先讲一下值转换器
就是一个继承了 IValueConverter接口的类,实现他们 主要实现 Convert方法 另一个方法可以保持默认,这里有2个值转换器,一个是将string转换成int
还有1个是将double转换成int的
转换器写好了,前台就可以 在绑定时将path后面的值作为条件转换成另一种类型的值了,例如:
{Binding ElementName=FontSlider,
Path=Value,
Converter={StaticResource DoubleConverterResource}
值转换器这里 我就不说了,以后我还会写文章介绍的
老朋友了,后台 定义2个 CanExecute的方法和Executed方法就行了,这里可以参考前面4章理解 就知道我为什么要定义这2个方法了;
前台代码
<local:CommandSlider x:Name="FontSlider"
Background="AliceBlue"
Minimum="0"
Maximum="60"
Value="12"
TickFrequency="5"
Height="50"
Width="275"
TickPlacement="BottomRight"
LargeChange="5"
SmallChange="5"
AutoToolTipPlacement="BottomRight"
AutoToolTipPrecision="0"
Command="{x:Static local:CustomControlWithCommand.FontUpdateCommand}"
CommandTarget="{Binding ElementName=txtBoxTarget}"
CommandParameter="{Binding ElementName=FontSlider,
Path=Value,
Converter={StaticResource DoubleConverterResource}}"
Focusable="False"/>
很清楚的就看到了Command CommandTarget CommandParameter这3个依赖性属性了 对应了 路由命令类型的命令 路由目标 下面是一个参数 后面还有一个Converter就是转换器了
<TextBox Name="txtBoxTarget" Height="100" Width="275" Margin="3">
<TextBox.CommandBindings>
<CommandBinding Command="{x:Static local:CustomControlWithCommand.FontUpdateCommand}"
Executed="SliderUpdateExecuted"
CanExecute="SliderUpdateCanExecute" />
</TextBox.CommandBindings>
文本框调用方法
这是一篇关于 CommandManager.InvalidateRequerySuggested() 的讲解的链接,我看了还不错,不理解的可以看一下
http://www.cnblogs.com/mgen/archive/2011/05/29/2062170.html