在不违反项目分离的情况下配置 ItemTemplate 的标准方法是什么?

What is the standard way to configure an ItemTemplate without violating the Item separation?

在使用 WPF 一段时间后,这实际上是我第一次遇到这样的情况:我有一个 ListBox 的 ItemTemplate,我想根据项目本身之外的属性对其进行配置。当我想要一个字体选择器对话框,用户可以在其中单击一个复选框以启用字体预览时,问题就出现了(我实际上改变了我对这个实现的想法,但我仍然想知道答案)。

看起来,因为 DataTemplate 可以在提供该类型的任何情况下使用,所以不绑定到项目配置之外的任何参数被认为是一种很好的做法(似乎代码绑定到包含DataTemplate 特别迟钝。)

我想知道我应该如何实现这种情况。下面的代码有效,但绑定到一个可视元素,而我宁愿绑定到 ViewModel 中的 属性。

<Window x:Class="ScreenWriter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" DataContext="{Binding Mode=OneWay, RelativeSource={RelativeSource Self}}" >
    <Grid>
        <StackPanel>
            <CheckBox Name="ShowPreview" IsChecked="{Binding IsShowPreviewChecked}">
                Show Preview
            </CheckBox>
            <ListBox ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}">
                            <TextBlock.Style>
                                <Style>
                                    <Setter Property="TextBlock.FontFamily" Value="Arial" />
                                    <Style.Triggers>
                                        <DataTrigger Value="True" Binding="{Binding IsChecked, ElementName=ShowPreview}">
                                            <Setter Property="TextBlock.FontFamily" Value="{Binding}"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </TextBlock.Style>
                        </TextBlock>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </StackPanel>
    </Grid>
</Window>

这似乎不是异常情况,但我找不到任何不以 "this is a clever way to get around what you're not supposed to do" 为前缀的解决方案。 感谢您的帮助...

这个问题没有 'best practice' 解决方案。

如您所写,"the DataTemplate may be used in any situation where that type is provided ... not to bind to any parameters outside the item's configuration"。

Binding/DataContext 基于 LogicalTree。所以你的触发器绑定试图在树上找到最近的数据上下文(在这种情况下你的 ListItem DataContext = FontFamily )

你必须从这个 'LogicalTree Island' 到主树做同样的 'jump' - 就像你的 control/element 跳了一样。 所以真正的问题是,如何才能顺利更改绑定源。

您可以直接绑定目标元素 DataContext(CheckBox 没有 DataContext,但它的祖父元素 - Windows 有):

<DataTrigger Binding="{Binding DataContext.IsShowPreviewChecked, ElementName=ShowPreview}" Value="True" >    

或者直接查找ListItem的祖先:

<DataTrigger Binding="{Binding DataContext.IsShowPreviewChecked, RelativeSource={RelativeSource AncestorType=ListBox, Mode=FindAncestor}}" Value="True" >

如果你会多次使用这个跳转,你会尝试使用BindingProxy 这将为您的绑定提供快捷方式:

public class BindingProxy : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    public object Context
    {
        get { return (object)GetValue(ContextProperty); }
        set { SetValue(ContextProperty, value); }
    }

    public static readonly DependencyProperty ContextProperty =
        DependencyProperty.Register("Context", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}    

将此代理添加为资源:

<Window.Resources>
    <this:BindingProxy x:Key="Proxy" Context="{Binding}" />
</Window.Resources> 

您的绑定是:

<DataTrigger Binding="{Binding Context.IsShowPreviewChecked, Source={StaticResource Proxy}}" Value="True" >    

不要忘记将 INotifyPropertyChanged 接口添加到您的 ViewModel