.NET|--WPF|--笔记合集|--依赖项属性|--5.附加属性
前言
附加属性是一个 Extensible Application Markup Language (XAML) 概念。
附加属性允许为派生自 DependencyObject 的任何 XAML 元素设置额外的属性/值对,即使该元素未在其对象模型中定义这些额外的属性。
额外的属性可进行全局访问。
附加属性通常定义为没有常规属性包装器的依赖属性的专用形式。
附加属性 Vs 依赖项属性
附加属性是 XAML 概念,依赖属性是 WPF 概念。
在 WPF 中,WPF 类型上大多数与 UI 相关的附加属性都作为依赖属性实现。
作为依赖属性实现的 WPF 附加属性支持依赖属性概念,例如包含元数据中的默认值的属性元数据。
附加属性是一种依赖项属性, 由WPF属性系统管理.
不同之处在于附加属性被应用到的类, 并非定义附加属性的那个类.
# 比如, 下面的例子, 套入DockPanel和TextBox来解读下就是
# 不同之处在于(DockPanel的)附加属性被应用到的类(TextBox),并非定义附加属性的那个类(DockPanel).
<DockPanel>
<TextBox DockPanel.Dock="Top">Enter text</TextBox>
</DockPanel>
附加属性用法模型
附加属性的设定虽然可以为各种对象赋值,但这并不一定会产生具体的结果,也不一定会被其他对象使用。附加属性的主要目的是为来自各种类层次和逻辑关系的对象提供一种方法,将公共信息报告给定义附加属性的类型。附加属性的使用通常遵循以下三种模式:
-
父元素模式:定义附加属性的类型是为附加属性设置值的元素的父级。
父类型通过作用于对象树结构的内部逻辑,循环访问其子对象,获取值,并以某种方式处理这些值。 -
子元素模式:定义附加属性的类型作为各种可能的父元素和内容模型的子元素。
-
服务模式:定义附加属性的类型表示一种服务。其他类型为该附加属性设置值,然后,当在服务的上下文中计算设置该属性的元素时,通过服务类的内部逻辑获取附加属性的值。
总之,附加属性的使用遵循这三种模式,才能发挥其作用。
父类型模式举例
<DockPanel>
<TextBox DockPanel.Dock="Top">Enter text</TextBox>
</DockPanel>
# DockPanel的源码
[CommonDependencyProperty]
public static readonly DependencyProperty DockProperty;
static DockPanel()
{
// 省略...
DockProperty = DependencyProperty.RegisterAttached("Dock", typeof(Dock), typeof(DockPanel), new FrameworkPropertyMetadata(Dock.Left, OnDockChanged), IsValidDock);
}
// 定义附加属性的类型(DockPanel)是为附加属性(DockProperty)设置值的元素(TextBox)的父级...
// 重点是分清父子关系, 该例子中, 父级是DockPanel, 子级是TextBox, 附加属性Dock, 定义在父级DockPanel类中, 该模式为父类型模式.
子元素模式
<!-- TextElement.FontSize -->
<!--可以简略这么写:<StackPanel TextElement.FontSize="20">, 但是这么写,不容易看出TextElement为StackPanel的子元素.-->
<StackPanel >
<!--定义附加属性(FontSize)的类型(TextElement)作为各种可能的父元素(StackPanel)的子元素。-->
<TextElement.FontSize>
20
</TextElement.FontSize>
<!--单一内容模型的控件只能包含一个子元素。例如 Button、ContentControl 和 Label。-->
<Button TextElement.FontSize="10" Content="定义附加属性的类型(TextElement)作为各种可能的父元素和内容模型的子元素。"/>
<!--如果上面的Button看着不太好理解, 换为下种表达方式可能会更好看出来TextElement作为内容模型Button的子元素-->
<Button>
<TextElement.FontSize>
10
</TextElement.FontSize>
<Button.Content>
定义附加属性(FontSize)的类型(TextElement)作为各种可能的内容模型(Button)的子元素。
</Button.Content>
</Button>
<TextBlock>FontSize为20, 继承自StackPanel</TextBlock>
</StackPanel>
// TextElement源码
[CommonDependencyProperty]
public static readonly DependencyProperty FontFamilyProperty;
static TextElement()
{
FontSizeProperty = DependencyProperty.RegisterAttached("FontSize", typeof(double), typeof(TextElement), new FrameworkPropertyMetadata(SystemFonts.MessageFontSize, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Inherits), IsValidFontSize);
}
// 定义附加属性的类型(TextElement)作为各种可能的父元素和内容模型的子元素。
// 这段不太懂, 又是父元素, 又是子元素的~
// 我本来的断句是
// 定义附加属性的类型(TextElement) / 作为各种可能的父元素 / 和 / 内容模型的子元素。
// 正确的断句应该是↓?
// 定义附加属性的类型(TextElement) / 作为各种可能的 / 父元素和内容模型的 / 子元素。
// 重点是分清父子关系, 该例子中, 父级是StackPanel, 子级是TextBox, 附加属性Dock, 定义在父级DockPanel类中, 该模式为父类型模式.
服务模式
WPF的拖放操作中使用附加属性存储和传递数据。
例如,DragDrop类定义了一些附加属性,
如DragDrop.DataContextProperty,用于在拖放过程中存储和获取数据。
在这个例子中,DragDrop类通过附加属性存储拖动的数据,
当放下数据时,通过Drop事件处理程序访问这些数据。
<!-- Drop="Window_Drop" -->
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="300"
AllowDrop="True"
Drop="Window_Drop">
<Grid>
<TextBlock Text="拖动一些文本到这里" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Window>
// MainWindow.xaml.cs
using System.Windows;
namespace WpfApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
{
string droppedText = (string)e.Data.GetData(DataFormats.Text);
MessageBox.Show($"你拖放了: {droppedText}");
}
}
}
}
总结 :
其实这3中模式, 服务模式比较好辨认, 在"父元素模式"和"子元素模式"哪里, 其实我还是懵了一下, , ,
XAML附加属性 Vs 代码实现附加属性
<!-- xaml中附加属性使用 -->
<DockPanel>
<TextBox DockPanel.Dock="Top">Enter text</TextBox>
</DockPanel>
// LINQPad中实现
void Main()
{
System.Windows.Application app = new System.Windows.Application();
var mainWindow = new MainWindows
{
Title = "依赖项属性-附加属性-DockPanel演示",
Width = 400,
Height = 250,
WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen
};
app.Run(mainWindow);
}
// 主窗口
public class MainWindows : System.Windows.Window
{
public MainWindows()
{
Init();
}
public void Init()
{
// 创建 DockPanel 实例
DockPanel dockPanel = new DockPanel();
// 创建 TextBox 实例
TextBox textBox = new TextBox
{
Text = "Enter text"
};
// 将 TextBox 的 DockPanel.Dock 属性设置为 Top
DockPanel.SetDock(textBox, Dock.Top);
// 将 TextBox 添加到 DockPanel 中
dockPanel.Children.Add(textBox);
// 将 DockPanel 设置为窗口的内容
this.Content = dockPanel;
}
}
附加属性--创建
// LINQPad中可以运行
void Main()
{
System.Windows.Application app = new System.Windows.Application();
var mainWindow = new MainWindows
{
Title = "依赖项属性-附加属性-DockPanel演示",
Width = 400,
Height = 250,
WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen
};
app.Run(mainWindow);
}
// 主窗口
public class MainWindows : System.Windows.Window
{
public MainWindows()
{
Init();
}
public void Init()
{
// 创建 TextBox 实例
System.Windows.Controls.TextBox textBox = new System.Windows.Controls.TextBox
{
Text = "演示创建附加属性, 以及设置/获取, 附加属性得值\r\n"
};
// 将 TextBox 的 DockPanel.Dock 属性设置为 Top
System.Windows.Controls.DockPanel.SetDock(textBox, System.Windows.Controls.Dock.Top);
Aquarium aquarium = new Aquarium();
textBox.Text += ($"Aquarium.Fish.默认值为:{Aquarium.GetHasFish(textBox)}\r\n");
Aquarium.SetHasFish(textBox, true);
textBox.Text += ($"Aquarium.Fish.设置为:{Aquarium.GetHasFish(textBox)}\r\n");
// 感兴趣得可以看一下,如何会根据设置得Dock, 而显示得位置不同~
textBox.Text += ($"但是是不是感觉哪里不对?只是设置了值,但是并没有具体得业务逻辑,比如DockPanel设置了Dock.Top,就会显示在Top.\r\n咱们这里只是演示,设置了true,打印出来true即可...\r\n");
this.Content = textBox;
}
}
// 官方示例👇
public class Aquarium : System.Windows.UIElement
{
// Register an attached dependency property with the specified
// property name, property type, owner type, and property metadata.
public static readonly System.Windows.DependencyProperty HasFishProperty =
System.Windows.DependencyProperty.RegisterAttached(
"HasFish",
typeof(bool),
typeof(Aquarium),
new System.Windows.FrameworkPropertyMetadata(defaultValue: false,
flags: System.Windows.FrameworkPropertyMetadataOptions.AffectsRender)
);
// Declare a get accessor method.
public static bool GetHasFish(System.Windows.UIElement target) =>
(bool)target.GetValue(HasFishProperty);
// Declare a set accessor method.
public static void SetHasFish(System.Windows.UIElement target, bool value) =>
target.SetValue(HasFishProperty, value);
}