如何将元数据与 WPF 中的许多不同类型的控件相关联?
How to associate metadata with many different types of controls in WPF?
我有一堆控件可能会或可能不会根据其他控件中的错误启用。当控件被禁用时,我想向用户解释为什么它当前被禁用。但是,我已经在使用 ToolTip 来获取帮助文本。所以我想当用户将鼠标悬停在灰色组件上时,我会在静态文本框中显示错误消息。
简化xaml:
<StackPanel Margin="10 10 10 10">
<TextBox x:Name="thtkDirTextBox" ToolTip="yadda yadda help text"/>
<TextBox x:Name="tououDatTextBox" ToolTip="yadda yadda help text"/>
<ComboBox x:Name="touhouExeSelector" ToolTip="yadda yadda help text"/>
<ComboBox x:Name="stageSelector" ToolTip="yadda yadda help text"/>
<ComboBox x:Name="sceneSelector" ToolTip="yadda yadda help text"/>
<GroupBox Header="Errors">
<TextBox IsEnabled="False" TextWrapping="Wrap" Width="200" FontSize="10" Height="50">
Mouse over an item that's grayed out and the reason why will appear here.
</TextBox>
</GroupBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Save as..." ToolTip="yadda yadda help text"></Button>
<Button Content="Quick Play" ToolTip="yadda yadda help text"></Button>
</StackPanel>
</StackPanel>
至关重要的是,为了使其正常工作,每个组件都需要与自定义的元数据片段相关联以保存其错误消息。 (不同控件变灰的原因可能不同!)
我阅读了有关 DependencyProperties 的内容,但似乎使用它们需要对控件进行子类化。如果我必须创建 TextBox
、ComboBox
和 Button
的子类,那么我最终会在三个不同的不兼容的 DependencyProperties 中得到重复的代码。 (另外,几乎所有的例子都涉及触发器的使用,我认为这对我没有帮助?)
我看到还有一个Tag
属性。这是 属性 的合理用法吗?如果我后来发现我需要另一个标签 属性 怎么办?我是否应该假设这可能不会发生? (或者如果最终确实如此,也许将 JSON 对象序列化到 Tag 中以存储多个属性也许不是不合理的?)
我应该在这里做什么?
其他详细信息:
我正在使用 ReactiveUI 将信息从一个控件传播到另一个控件。对于每个可以变灰的控件,ViewModel 最终构造一个 IObservable
,其项目要么是控件所需的某些值(例如,一个 IEnumerable<string>
带有组合框的选项,或者可能只是一个 Unit
作为按钮有效性的证明)或错误消息。
给定这些可观察量之一,我可以轻松地 OneWayBind
类型 bool
的 ObservableAsPropertyHelper
到控件的 IsEnabled
属性,并希望这样做错误消息类似的东西。如果我为此使用工具提示,那将相当简单:
// ViewModel
this.stageSelectorErrorMessage =
this.stageSelectorEithers
.Select(either => either.Error) // string on failure, null on success
.ScheduleOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.StageSelectorErrorMessage);
// View
this.WhenActivated(disposableRegistration => {
// ...
this.OneWayBind(this.ViewModel,
viewModel => viewModel.StageSelectorErrorMessage,
view => view.stageSelector.ToolTip
).DisposeWith(disposableRegistration);
})
我还没有决定如何连接鼠标悬停事件。 (还没到那一步)
I see there is also a Tag
property. Is this a fair usage of the property?
更好的方法是创建一个可以在任何元素上设置的强类型 attached dependency property。只需在注册 属性:
的地方定义一个静态 class
public static class Metadata
{
public static readonly DependencyProperty InfoProperty = DependencyProperty.RegisterAttached(
"Info",
typeof(string),
typeof(Metadata),
new FrameworkPropertyMetadata(null));
public static void SetInfo(UIElement element, string value) => element.SetValue(InfoProperty, value);
public static string GetInfo(UIElement element) => (string)element.GetValue(InfoProperty);
}
您可以像这样在任何 UIElement
上设置它:
<TextBox IsEnabled="False" TextWrapping="Wrap" Width="200" FontSize="10" Height="50"
local:Metadata.Info="Mouse over an item that's grayed out and the reason why will appear here.">
</TextBox>
您使用 GetInfo
方法以编程方式获取值:
string info = Metadata.GetInfo(theControl);
我有一堆控件可能会或可能不会根据其他控件中的错误启用。当控件被禁用时,我想向用户解释为什么它当前被禁用。但是,我已经在使用 ToolTip 来获取帮助文本。所以我想当用户将鼠标悬停在灰色组件上时,我会在静态文本框中显示错误消息。
简化xaml:
<StackPanel Margin="10 10 10 10">
<TextBox x:Name="thtkDirTextBox" ToolTip="yadda yadda help text"/>
<TextBox x:Name="tououDatTextBox" ToolTip="yadda yadda help text"/>
<ComboBox x:Name="touhouExeSelector" ToolTip="yadda yadda help text"/>
<ComboBox x:Name="stageSelector" ToolTip="yadda yadda help text"/>
<ComboBox x:Name="sceneSelector" ToolTip="yadda yadda help text"/>
<GroupBox Header="Errors">
<TextBox IsEnabled="False" TextWrapping="Wrap" Width="200" FontSize="10" Height="50">
Mouse over an item that's grayed out and the reason why will appear here.
</TextBox>
</GroupBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Save as..." ToolTip="yadda yadda help text"></Button>
<Button Content="Quick Play" ToolTip="yadda yadda help text"></Button>
</StackPanel>
</StackPanel>
至关重要的是,为了使其正常工作,每个组件都需要与自定义的元数据片段相关联以保存其错误消息。 (不同控件变灰的原因可能不同!)
我阅读了有关 DependencyProperties 的内容,但似乎使用它们需要对控件进行子类化。如果我必须创建 TextBox
、ComboBox
和 Button
的子类,那么我最终会在三个不同的不兼容的 DependencyProperties 中得到重复的代码。 (另外,几乎所有的例子都涉及触发器的使用,我认为这对我没有帮助?)
我看到还有一个Tag
属性。这是 属性 的合理用法吗?如果我后来发现我需要另一个标签 属性 怎么办?我是否应该假设这可能不会发生? (或者如果最终确实如此,也许将 JSON 对象序列化到 Tag 中以存储多个属性也许不是不合理的?)
我应该在这里做什么?
其他详细信息:
我正在使用 ReactiveUI 将信息从一个控件传播到另一个控件。对于每个可以变灰的控件,ViewModel 最终构造一个 IObservable
,其项目要么是控件所需的某些值(例如,一个 IEnumerable<string>
带有组合框的选项,或者可能只是一个 Unit
作为按钮有效性的证明)或错误消息。
给定这些可观察量之一,我可以轻松地 OneWayBind
类型 bool
的 ObservableAsPropertyHelper
到控件的 IsEnabled
属性,并希望这样做错误消息类似的东西。如果我为此使用工具提示,那将相当简单:
// ViewModel
this.stageSelectorErrorMessage =
this.stageSelectorEithers
.Select(either => either.Error) // string on failure, null on success
.ScheduleOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.StageSelectorErrorMessage);
// View
this.WhenActivated(disposableRegistration => {
// ...
this.OneWayBind(this.ViewModel,
viewModel => viewModel.StageSelectorErrorMessage,
view => view.stageSelector.ToolTip
).DisposeWith(disposableRegistration);
})
我还没有决定如何连接鼠标悬停事件。 (还没到那一步)
I see there is also a
Tag
property. Is this a fair usage of the property?
更好的方法是创建一个可以在任何元素上设置的强类型 attached dependency property。只需在注册 属性:
的地方定义一个静态 classpublic static class Metadata
{
public static readonly DependencyProperty InfoProperty = DependencyProperty.RegisterAttached(
"Info",
typeof(string),
typeof(Metadata),
new FrameworkPropertyMetadata(null));
public static void SetInfo(UIElement element, string value) => element.SetValue(InfoProperty, value);
public static string GetInfo(UIElement element) => (string)element.GetValue(InfoProperty);
}
您可以像这样在任何 UIElement
上设置它:
<TextBox IsEnabled="False" TextWrapping="Wrap" Width="200" FontSize="10" Height="50"
local:Metadata.Info="Mouse over an item that's grayed out and the reason why will appear here.">
</TextBox>
您使用 GetInfo
方法以编程方式获取值:
string info = Metadata.GetInfo(theControl);