DataTrigger中的绑定似乎无法正常工作

时间:2016-02-03 10:10:01

标签: c# wpf datatemplate datatrigger

我有一个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; }
}

和MainWindow:

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>

这给出了以下内容: Not expected result

但它看起来应该类似于: enter image description here

3 个答案:

答案 0 :(得分:1)

请首先考虑下一句中提供的事实 - 您只能选择ComboBox ItemsSource提供的项目。因此,由于Name属性值(Model1,Model2,Model3等)不在您的集合中,因此无法选择,您将看到空的选择。我可以建议你下一个解决方案数据上下文代理和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();
    }
}

这是一个完整的解决方案,只需复制/过去,并将其作为您进一步研究的起点。如果您对代码有任何问题,我很乐意提供帮助。

问候。

答案 1 :(得分:1)

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

更改ComboBox模板

enter image description here

通过右键单击编辑标准组合框模板在设计器中单击组合框并选择编辑模板 - &gt;编辑副本... 之后,使用自定义转换器更改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&amp; IsReadOnly并更改文本 enter image description here

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

答案 2 :(得分:0)

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

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

然后,与其他代码一起,它就像预期的那样工作。