DataTrigger 中的绑定似乎不起作用

Binding in DataTrigger seems not working

我有一个 ComboBox,当没有选择任何内容时它应该有一个文本。这似乎是一个简单的问题,网上有很多答案,但不幸的是,它对我不起作用。我认为,原因是我不想显示静态文本,而是显示绑定文本。

我的最小无效示例如下所示:

public class Model
{
    public string Name { get; set; }

    public SubModel SelectedItem { get; set; }

    public List<SubModel> Items { get; set; }
}

public class SubModel
{
    public string Description { get; set; }
}

和主窗口:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var selectedSubModel = new SubModel { Description = "SubModel5" };
        var model1 = new Model
        {
            Name = "Model1",
            Items = new List<SubModel>
                {
                    new SubModel { Description = "SubModel1" },
                    new SubModel { Description = "SubModel2" },
                    new SubModel { Description = "SubModel3" }
                }
        };
        var model2 = new Model
        {
            Name = "Model2",
            SelectedItem = selectedSubModel,
            Items = new List<SubModel>
                {
                    new SubModel { Description = "SubModel4" },
                    selectedSubModel,
                    new SubModel { Description = "SubModel6" }
                }
        };
        var model3 = new Model
        {
            Name = "Model3",
            Items = new List<SubModel>
                {
                    new SubModel { Description = "SubModel7" },
                    new SubModel { Description = "SubModel8" },
                    new SubModel { Description = "SubModel9" }
                }
        };

        _itemsControl.Items.Add(model1);
        _itemsControl.Items.Add(model2);
        _itemsControl.Items.Add(model3);
    }
}

与 xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:WpfApplication1="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <ItemsControl x:Name="_itemsControl">
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="WpfApplication1:Model">
                <ComboBox ItemsSource="{Binding Items}"
                        SelectedItem="{Binding SelectedItem}">
                    <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Description}"></TextBlock>
                        </DataTemplate>
                    </ComboBox.ItemTemplate>
                    <ComboBox.Style>
                        <Style TargetType="ComboBox">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
                                    <Setter Property="Background">
                                        <Setter.Value>
                                            <VisualBrush>
                                                <VisualBrush.Visual>
                                                    <TextBlock Text="{Binding Name}"/>
                                                </VisualBrush.Visual>
                                            </VisualBrush>
                                        </Setter.Value>
                                    </Setter>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </ComboBox.Style>
                </ComboBox>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

这给出了以下内容:

但它看起来应该类似于:

请首先记住下一句中提供的事实 - 您只能 select ComboBox ItemsSource 提供的项目。因此,由于名称 属性 值(Model1、Model2、Model3 等)不在您的集合中,因此无法对它们进行 selected,您将看到空的 selection。我可以建议您下一个解决方案,即数据上下文代理和 wpf 行为的组合。

Xaml代码

<Window x:Class="ComboBoxWhenNoAnySelectedHelpAttempt.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:comboBoxWhenNoAnySelectedHelpAttempt="clr-namespace:ComboBoxWhenNoAnySelectedHelpAttempt"
    xmlns:system="clr-namespace:System;assembly=mscorlib"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <ItemsControl x:Name="_itemsControl">
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="comboBoxWhenNoAnySelectedHelpAttempt:Model">
                <ComboBox x:Name="ComboBox"
                    SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}">
                    <ComboBox.Resources>
                        <!--the next object is a proxy that able to provide combo data context each time it requested-->
                        <comboBoxWhenNoAnySelectedHelpAttempt:FreezableProxyClass x:Key="FreezableProxyClass" ProxiedDataContext="{Binding ElementName=ComboBox, Path=DataContext }"></comboBoxWhenNoAnySelectedHelpAttempt:FreezableProxyClass>
                    </ComboBox.Resources>
                    <ComboBox.ItemsSource>
                        <CompositeCollection>
                            <!--the next object is a collapsed combo box that can be selected in code-->
                            <!--keep im mind, since this object is not a SubModel we get the binding expression in output window-->
                            <ComboBoxItem IsEnabled="False" Visibility="Collapsed" Foreground="Black" Content="{Binding Source={StaticResource FreezableProxyClass}, 
                Path=ProxiedDataContext.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBoxItem>
                            <CollectionContainer Collection="{Binding Source={StaticResource FreezableProxyClass}, 
                Path=ProxiedDataContext.Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                        </CompositeCollection>
                    </ComboBox.ItemsSource>
                    <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Description}"></TextBlock>
                        </DataTemplate>
                    </ComboBox.ItemTemplate>
                    <i:Interaction.Behaviors>
                        <!--next behavior helps to select a zero index (Model.Name collapsed) item from source when selected item is not SubModel-->
                        <comboBoxWhenNoAnySelectedHelpAttempt:ComboBoxLoadingBehavior/>
                    </i:Interaction.Behaviors>
                </ComboBox>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

代理代码隐藏代码

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


    public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
        "ProxiedDataContext", typeof(object), typeof(FreezableProxyClass), new PropertyMetadata(default(object)));

    public object ProxiedDataContext
    {
        get { return (object)GetValue(ProxiedDataContextProperty); }
        set { SetValue(ProxiedDataContextProperty, value); }
    }
}

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{

    public MainWindow()
    {
        InitializeComponent();
        var selectedSubModel = new SubModel { Description = "SubModel5" };
        var model1 = new Model
        {
            Name = "Model1",
            Items = new ObservableCollection<SubModel>
            {
                new SubModel { Description = "SubModel1" },
                new SubModel { Description = "SubModel2" },
                new SubModel { Description = "SubModel3" }
            }
        };
        var model2 = new Model
        {
            Name = "Model2",
            SelectedItem = selectedSubModel,
            Items = new ObservableCollection<SubModel>
            {
                new SubModel { Description = "SubModel4" },
                selectedSubModel,
                new SubModel { Description = "SubModel6" }
            }
        };
        var model3 = new Model
        {
            Name = "Model3",
            Items = new ObservableCollection<SubModel>
            {
                new SubModel { Description = "SubModel7" },
                new SubModel { Description = "SubModel8" },
                new SubModel { Description = "SubModel9" }
            }
        };

        _itemsControl.Items.Add(model1);
        _itemsControl.Items.Add(model2);
        _itemsControl.Items.Add(model3);
    }
}

public class Model:BaseObservableObject
{
    private string _name;
    private SubModel _selectedItem;
    private ObservableCollection<SubModel> _items;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public SubModel SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
            OnPropertyChanged();
        }
    }

    public ObservableCollection<SubModel> Items
    {
        get { return _items; }
        set
        {
            _items = value;
            OnPropertyChanged();
        }
    }
}

public class SubModel:BaseObservableObject
{
    private string _description;

    public string Description
    {
        get { return _description; }
        set
        {
            _description = value;
            OnPropertyChanged();
        }
    }
}

BaseObservableObject 代码(INotifyPropertyChanged 的​​简单实现)

    /// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
    {
        var propName = ((MemberExpression)raiser.Body).Member.Name;
        OnPropertyChanged(propName);
    }

    protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            OnPropertyChanged(name);
            return true;
        }
        return false;
    }
}

WPF 行为代码

public class ComboBoxLoadingBehavior:Behavior<ComboBox>
{
    private bool _unLoaded;

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += AssociatedObjectOnLoaded;
        AssociatedObject.LayoutUpdated += AssociatedObjectOnLayoutUpdated;
        AssociatedObject.Unloaded += AssociatedObjectOnUnloaded;
    }

    private void AssociatedObjectOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
    {
        _unLoaded = true;
        UnsubscribeAll();
    }

    private void UnsubscribeAll()
    {
        AssociatedObject.Loaded -= AssociatedObjectOnLoaded;
        AssociatedObject.LayoutUpdated -= AssociatedObjectOnLayoutUpdated;
        AssociatedObject.Unloaded -= AssociatedObjectOnUnloaded;
    }

    private void AssociatedObjectOnLayoutUpdated(object sender, EventArgs eventArgs)
    {
        UpdateSelectionState(sender);
    }

    private static void UpdateSelectionState(object sender)
    {
        var combo = sender as ComboBox;
        if (combo == null) return;
        var selectedItem = combo.SelectedItem as SubModel;
        if (selectedItem == null)
        {
            combo.SelectedIndex = 0;
        }
    }

    private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        _unLoaded = false;
        UpdateSelectionState(sender);

    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        if(_unLoaded) return;
        UnsubscribeAll();
    }
}

这是针对您的问题的有效完整解决方案,只是 copy/past 并将其用作您进一步研究的起点。如果您对代码有任何疑问,我很乐意提供帮助。

此致。

我找到了 2 种可能的解决方案:

更改组合框模板

通过右键单击设计器中的组合框和 select 编辑模板 -> 编辑副本来编辑标准组合框模板... 之后使用自定义转换器更改 ContentPresenter:

XAML

<ContentPresenter ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
    <ContentPresenter.Content>
        <MultiBinding Converter="{local:ComboboxEmptyValueConverter}">
            <Binding Path="SelectionBoxItem" RelativeSource="{RelativeSource Mode=TemplatedParent}" />
            <Binding Mode="OneWay" Path="DataContext" RelativeSource="{RelativeSource Mode=TemplatedParent}" />
        </MultiBinding>
    </ContentPresenter.Content>
</ContentPresenter>

C#

class ComboboxEmptyValueConverterExtension : MarkupExtension, IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        string stringValue = values[0] as string;
        var dataContext = values[1] as Model;

        return (stringValue != null && String.IsNullOrEmpty(stringValue)) ? dataContext?.Name : values[0];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return new object[] { value, null };
    }


    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

将 ComboBox 设置为 IsEditable & IsReadOnly 并更改 Text

<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock x:Name="textBlock" Text="{Binding Description}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
    <ComboBox.Style>
        <Style TargetType="ComboBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
                    <Setter Property="IsEditable" Value="True" />
                    <Setter Property="IsReadOnly" Value="True" />
                    <Setter Property="Text" Value="{Binding Name}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ComboBox.Style>
</ComboBox>

答案是,将可视化画笔放在组合框的资源中:

<DataTemplate DataType="WpfApplication1:Model">
    <ComboBox ItemsSource="{Binding Items}"
            SelectedItem="{Binding SelectedItem}">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Description}"></TextBlock>
            </DataTemplate>
        </ComboBox.ItemTemplate>
        <ComboBox.Resources>
            <VisualBrush x:Key="_myBrush">
                <VisualBrush.Visual>
                    <TextBlock Text="{Binding Name}"/>
                </VisualBrush.Visual>
            </VisualBrush>
        </ComboBox.Resources>
        <ComboBox.Style>
            <Style TargetType="ComboBox">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
                        <Setter Property="Background" Value="{StaticResource _myBrush}"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ComboBox.Style>
    </ComboBox>
</DataTemplate>

然后,与其余代码一起,它按预期工作。