逻辑树与可视树
逻辑树:WPF中,界面由一个对象树构建而成,这由于它具有层次化特征。
可视树:从逻辑树扩展,不是把每个元素当做一个节点黑盒,比如对于ListBox在逻辑树中为一不可分割节点,但在可视树概念中为多个对象(TextBox,Border等)复合而成。
使用System.Windows.LogicalTreeHelper和System.Windows.Media.VisualTreeHelper可方便的便利逻辑树和可视树。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WPF_Test.MainWindow"
Title="窗体"
SizeToContent="WidthAndHeight"
Background="OrangeRed">
<StackPanel>
<Label FontWeight="Bold" FontSize="20" Foreground="White">
WPF Unleashed
</Label>
<Label>Publishing</Label>
<Label>Publishing 2</Label>
<ListBox>
<ListBoxItem>List 1</ListBoxItem>
</ListBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button MinWidth="75" Margin="10">OK</Button>
</StackPanel>
<StatusBar>成功创造窗口</StatusBar>
</StackPanel>
</Window>
namespace WPF_Test
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
//构造函数,程序入口点
public MainWindow()
{
InitializeComponent();
GetLogicalTree(0, this);
}
#region 事件处理
//布局完成
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
//可视树在布局完成后才会有节点
GetVisualTree(0, this);
}
#endregion
#region 方法
private void GetLogicalTree(int depth, object obj)
{
Debug.WriteLine(new string(' ', depth) + obj);
if (!(obj is DependencyObject))
{
return;
}
foreach (object child in LogicalTreeHelper.GetChildren(obj as DependencyObject))
{
GetLogicalTree(depth + 1, child);
}
}
private void GetVisualTree(int depth, DependencyObject obj)
{
Debug.WriteLine(new string(' ',depth) + obj);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
GetVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
}
}
#endregion
}
}
依赖属性
WPF引入新属性类型,用来实现样式化,自动数据绑定,动画等。在任何时候都是依赖多个提供程序来判断它的值。其最大特征是其内建传递变更通知的能力。
1.依赖属性的实现
public partial class MainWindow : Window
{
//定义一依赖属性(必须public static)
//名称后缀为Property
public static DependencyProperty IsDefaultProperty;
//构造函数,程序入口点
public MainWindow()
{
//注册依赖属性名,属性类型,所有者类型,属性元数据(默认值,值改变后的回调函数)
MainWindow.IsDefaultProperty = DependencyProperty.Register("IsDefault",
typeof(bool), typeof(MainWindow),
new FrameworkPropertyMetadata(false, OnIsDefaultChanged));
}
//.Net属性包装器(可选)
//GetValue(),SetValue()来至控件底层基类System.Windows.DependencyObject,依赖属性的类必须继承
public bool IsDefault
{
get { return (bool)GetValue(MainWindow.IsDefaultProperty); }
set { SetValue(MainWindow.IsDefaultProperty, value); }
}
//依赖属性值改变,回调处理方法
private static void OnIsDefaultChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
//改变主窗体的背景颜色
MainWindow win = obj as MainWindow;
win.Background = Brushes.Purple;
}
//点击按钮改变依赖属性值
private void Button_Click(object sender, RoutedEventArgs e)
{
IsDefault = true;
}
}
2.变更通知
无论何时,只要依赖属性值变了,WPF就会自动根据属性元数据(metadata)触发一系列动作。
<Button MinWidth="75" Margin="10" Click="Button_Click">OK
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
注:人为原因,按钮Trigger对象仅在Style对象里使用
3.属性值继续
非传统面向对象的类继承,是指属性值由父节点向下沿着其子节点传递。
注:1.并不是每个依赖属性都参与属性值继承
2.有其他优先级更高的来源来设置这些值
4.对多个提供程序的支持
5.附加属性
依赖属性的特殊形式,可被有效添加到任何对象中
<StackPanel TextElement.FontSize="20">
<Button MinWidth="75" Margin="10">OK</Button>
</StackPanel>
路由事件
路由事件触发后,可以向上或向下遍历可视树和逻辑树,以简单而且持久的方式在每个元素上触发。
1.路由事件的实现
public partial class MainWindow : Window
{
//定义路由事件
//名称后缀Event
public static RoutedEvent ClickEvent;
public MainWindow()
{
//注册路由事件
MainWindow.ClickEvent = EventManager.RegisterRoutedEvent("Click",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MainWindow));
//添加一个事件订阅
Click += MainWindow_Click;
}
//.Net路由事件包装器(可选)
public event RoutedEventHandler Click
{
add { AddHandler(MainWindow.ClickEvent, value); }
remove { RemoveHandler(MainWindow.ClickEvent, value); }
}
//重写基类点击鼠标左键处理方法
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
//触发路由事件
RaiseEvent(new RoutedEventArgs(MainWindow.ClickEvent, this));
}
//路由事件处理方法
private void MainWindow_Click(object sender, RoutedEventArgs e)
{
MainWindow win = sender as MainWindow;
win.Background = Brushes.BurlyWood;
}
}
2.路由策略和事件处理程序
路右策略即事件触发遍历整棵元素树的方式:
Tunneling(管道传递):先在根元素触发,由根元素向下沿树传递,直到到达源元素为止。
Bubbling(冒泡):先在源元素触发,再沿着树向上传递,直到到达根元素为止。
Direct(直接):仅在源元素上触发。
3.路由事件实践
管道事件:Preview前缀,在它们的配对冒泡事件发生前,这些事件会立刻触发。
在事件处理程序中设置RoutedEventArgs参数的Handled属性为true,可以终止管道传递或冒泡。
4.附加事件
每个路由事件都可以当做附加事件使用
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WPF_Test.MainWindow"
Title="窗体"
SizeToContent="WidthAndHeight"
Background="OrangeRed"
ButtonBase.Click="MainWindow_Click">
<Button>dd</Button>
</Window>
命令
WPF提供内建命令支持
1.内建命令
命令是任何实现System.Windows.Input.ICommand接口的对象。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WPF_Test.MainWindow">
<!-- 指定一内建命令,点击F1也能触发 -->
<Window.CommandBindings>
<CommandBinding Command="Help" CanExecute=" HelpCanExecute"
Executed="HelpExecute" />
</Window.CommandBindings>
</Window>
#region 内建命令
protected void HelpCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
protected void HelpExecute(object sender, ExecutedRoutedEventArgs e)
{
MainWindow win = sender as MainWindow;
win.Background = Brushes.BurlyWood;
}
#endregion
2.使用输入手势执行命令
以上默认Help命名是绑定F1,入需指定,可以:
<Window.InputBindings>
<KeyBinding Command="Help" Key="F2"/>
</Window.InputBindings>
F1,F2都将响应Help命令