首页 > 解决方案 > 如何将元数据与 WPF 中许多不同类型的控件相关联?

问题描述

我有一堆控件可能会或可能不会根据其他控件中的错误启用。当控件被禁用时,我想向用户解释它当前被禁用的原因。但是,我已经在使用 ToolTip 来获取帮助文本。所以我想当用户将鼠标悬停在灰色的组件上时,我会在静态文本框中显示错误消息。

简化的xml:

<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,那么我将在三个不同的不兼容的 DependencyProperties 中得到重复的代码。(此外,几乎所有的例子都涉及到触发器的使用,我认为这在这里对我没有帮助?)ComboBoxButton

我看到还有一个Tag属性。这是对财产的公平使用吗?如果我后来发现我需要另一个 Tag 属性怎么办?我应该假设这可能不会发生吗?(或者,如果最终确实如此,也许将 JSON 对象序列化为 Tag 以存储多个属性并不是不合理的?)

我应该在这里做什么?


额外细节:

我正在使用 ReactiveUI 将信息从一个控件传播到另一个控件。对于每个可以变灰的控件,ViewModel 最终都会构造一个IObservable,其项目要么是控件所需的某个值(例如,IEnumerable<string>带有选项的 ComboBox,或者可能只是Unit作为按钮有效性的证明),要么是错误信息。

给定这些 observable 之一,我可以轻松地为控件的属性添加OneWayBind一个ObservableAsPropertyHelper类型,并希望对错误消息执行类似的操作。如果我为此使用工具提示,那将相当简单:boolIsEnabled

// 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);
})

我还没有决定如何连接鼠标悬停事件。(还没走到那一步)

标签: wpfxamluser-interfacereactiveui

解决方案


我看到还有一个Tag属性。这是对财产的公平使用吗?

更好的方法是创建一个可以在任何元素上设置的强类型附加依赖属性。只需定义一个注册属性的静态类:

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);

推荐阅读