WPF 切换组合框项的文字颜色 MVVM ReactiveUI

5

好的,这是一个新手问题,不幸的是我无法找到答案。

基本上,当对象的Disabled属性设置为true时,将对象列表绑定到Combobox时,我希望Combobox项目的文本颜色设置为灰色。

这是我目前的代码:

Combobox项目数据类型

public class ListItem
{
    public ListItem(string text)
    {
        Text = text;
    }

    public string Text { get; set; }
    public bool Disabled { get; set; }
}

视图模型设置

public class MainPageViewModel : ReactiveObject
{
    // In ReactiveUI, this is the syntax to declare a read-write property
    // that will notify Observers, as well as WPF, that a property has 
    // changed. If we declared this as a normal property, we couldn't tell 
    // when it has changed!
    private ListItem _selectedItem;
    public ListItem SelectedItem
    {
        get => _selectedItem;
        set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
    }

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

    public MainPageViewModel()
    {
        Items = new List<ListItem>
        {
            new ListItem ("A Cat"),
            new ListItem ("A Dog"),
            new ListItem ("A Mouse"),
            new ListItem ("A Frog") { Disabled = true }
        };
    }
}

ReactiveUI绑定

public MainPage()
{
  InitializeComponent();

  ViewModel = new MainPageViewModel();
  
  this.WhenActivated(d =>
  {
    this.OneWayBind(ViewModel, vm => vm.Items, v => v.MyComboBox.ItemsSource)
                    .DisposeWith(d);

    this.Bind(ViewModel, vm => vm.SelectedItem, v => v.MyComboBox.SelectedItem)
                    .DisposeWith(d);

  });
}

Xaml标记语言

<ComboBox
 Name="MyComboBox"
 Margin="0,0,0,20"
 Foreground="black">
   <ComboBox.ItemTemplate>
     <DataTemplate>
       <TextBlock Text="{Binding}" />
     </DataTemplate>
   </ComboBox.ItemTemplate>

   <ComboBox.ItemContainerStyle>
     <Style TargetType="ComboBoxItem">
       <Style.Triggers>
         <DataTrigger Binding="{Binding Path=Disabled}" Value="True">
           <Setter Property="Foreground" Value="Gray" />
         </DataTrigger>
       </Style.Triggers>
     </Style>
    </ComboBox.ItemContainerStyle>

 </ComboBox>

任何帮助都是欢迎的,请让我知道如果您需要更多信息。


解决方案:看起来在未来我需要在发布示例代码之前测试一下 - 我们实际的代码将 Disabled 属性设置为 readonly,这可能会影响 WPF 绑定。将它改为公共的 set 和 get 就解决了第一个问题,看不到变灰!似乎长时间盯着一个问题会使你失明,而问题实际上是如此简单。 至于使选定的项目变灰,我将尝试一下并查看结果。


你在运行时更改了 Disabled 属性吗?据我所知,您没有实现 INotifyPropertyChanged 接口来通知任何更改。此外,您说当 Disabledtrue 时要将颜色更改为 Gray,但在您的 XAML 中,它触发的是 False。如果您创建一个项并将其 Disabled 属性最初设置为触发器值,那么它是否有效? - thatguy
@thatguy 抱歉,我应该表述得更清楚。当 ViewModel 被构建时,我们基本上会构建一个 List<ListItem>,其中一些项将设置 Disabled 属性为 true。至于第二部分,那是一个打字错误,我在尝试让它工作 - 你是对的,它应该是 Value="True",我会更新代码。 - ree6
你能解决你的问题吗? - Funk
3个回答

4
下拉列表中的最后一项已经被灰化了,所以我认为您是在询问所选项目。ComboBox使用不同的数据模板来设置所选项目和下拉列表中的项目。您可以使用DataTemplateSelector来设置两者。
public class ComboBoxTemplateSelector : DataTemplateSelector
{
    public DataTemplate SelectedItemTemplate { get; set; }
    public DataTemplate DropdownItemsTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var itemToCheck = container;

        // Search up the visual tree, stopping at either a ComboBox or a ComboBoxItem (or null).
        // This will determine which template to use.
        while (itemToCheck is not null and not ComboBox and not ComboBoxItem)
            itemToCheck = VisualTreeHelper.GetParent(itemToCheck);

        // If you stopped at a ComboBoxItem, you're in the dropdown.
        return itemToCheck is ComboBoxItem ? DropdownItemsTemplate : SelectedItemTemplate;
    }
}
Xaml标记语言
<StackPanel>
    <StackPanel.Resources>
        <Style x:Key="GrayedOutText" TargetType="TextBlock">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Disabled}" Value="True">
                    <Setter Property="Foreground" Value="Gray" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
        <local:ComboBoxTemplateSelector x:Key="ComboBoxTemplateSelector">
            <local:ComboBoxTemplateSelector.SelectedItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}" Style="{StaticResource GrayedOutText}" />
                </DataTemplate>
            </local:ComboBoxTemplateSelector.SelectedItemTemplate>
            <local:ComboBoxTemplateSelector.DropdownItemsTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}" Style="{StaticResource GrayedOutText}" />
                </DataTemplate>
            </local:ComboBoxTemplateSelector.DropdownItemsTemplate>
        </local:ComboBoxTemplateSelector>
    </StackPanel.Resources>
    <ComboBox
         Name="MyComboBox"
         Margin="0,0,0,20"
         ItemTemplateSelector="{StaticResource ComboBoxTemplateSelector}">
    </ComboBox>
</StackPanel>

我们的DataTemplate定义中存在一些重复,但在实际生产代码中这些定义往往会分离开来。

资源

谢谢 - 你说得对,我的代码应该可以直接工作。当我更新了我的帖子后,我发现我发布的示例和产品代码之间的区别在于 Disabled 属性是只读的。呃!!! 再次感谢。接下来将尝试使用 SelectedItem 代码。 - ree6

1
我假设你的问题是应用程序运行后ComboBoxItem没有变灰。
我不熟悉ReactiveUI,但是由于在您的代码中发现了一个问题,我尝试在CommunityToolkit.Mvvm版本的代码中进行并验证了我的理论。
底线是,您需要实现Disabled属性的ReactiveUI版本的INotifyPropertyChanged
如果您感兴趣,我可以发布此代码的CommunityToolkit.Mvvm版本。

0
这里有一种在我的测试中有效的方法:
下拉框的项目数据类型:
//-- Unchanged
public class ListItem
{
    public ListItem( string text )
    {
        Text = text;
    }

    public string Text { get; set; }

    public bool Disabled { get; set; }
}

视图模型设置:

public class MainPageViewModel : INotifyPropertyChanged
{
    private ListItem? _selectedItem;

    public event PropertyChangedEventHandler? PropertyChanged;

    public ListItem? SelectedItem
    {
        get => _selectedItem;
        set
        {
            //-- I didn't had the "RaiseAndSetIfChanged" method, so I just implemented the functionality manually
            if( value != _selectedItem )
            {
                //-- Update the value ...
                _selectedItem = value;

                //-- ... AND inform everyone (who is interested) about the change
                this.PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( nameof( this.SelectedItem ) ) );
            }
        }
    }

    //-- Use always an ObservableCollection when you want to achieve reactivity
    public ObservableCollection<ListItem> Items 
    { get; } = new ObservableCollection<ListItem>();

    public MainPageViewModel()
    {
        //-- Add some test data
        this.Items.Add( new ListItem( "A Cat" ) );
        this.Items.Add( new ListItem( "A Dog" ) );
        this.Items.Add( new ListItem( "A Mouse" ) );
        this.Items.Add( new ListItem( "A Frog" ) { Disabled = true } );

        //-- Just select the first item
        this.SelectedItem = this.Items[0];
    }
}

主页:

public MainPage()
    {
        //-- Define the DataContext BEFORE the UI will be initialized ;)
        this.DataContext = new MainPageViewModel();

        InitializeComponent();

        //-- Never saw such code before -> just don't do that ;)

        //this.WhenActivated( d =>
        //{
        //    this.OneWayBind( ViewModel, vm => vm.Items, v => v.MyComboBox.ItemsSource )
        //                    .DisposeWith( d );

        //    this.Bind( ViewModel, vm => vm.SelectedItem, v => v.MyComboBox.SelectedItem )
        //                    .DisposeWith( d );

        //} );
    }

Xaml 标记:

<DockPanel>

    <ComboBox
        DockPanel.Dock="Top"
        Name="MyComboBox"
        Margin="0,0,0,20"
        Foreground="black"
        ItemsSource="{Binding Items}"
        SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Text}" />
            </DataTemplate>
        </ComboBox.ItemTemplate>

        <ComboBox.ItemContainerStyle>
            <Style TargetType="ComboBoxItem">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=Disabled}" Value="True">
                        <Setter Property="Foreground" Value="Gray" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ComboBox.ItemContainerStyle>

    </ComboBox>
    
    <!-- Details View -->
    <StackPanel>
        
        <!-- name -->
        <StackPanel Orientation="Horizontal">

            <Label Content="Item Name" />
            <TextBox Text="{Binding SelectedItem.Text}" />

        </StackPanel>

        <!-- disabled flag -->
        <StackPanel Orientation="Horizontal">

            <Label Content="IsDisabled" />
            <CheckBox IsChecked="{Binding SelectedItem.Disabled}" />
        </StackPanel>
    </StackPanel>   
</DockPanel>

我希望这能满足你的需求。玩得开心 :)


谢谢,我会看一下。顺便说一下,你注释掉的代码是ReactiveUI的ViewModel绑定代码 - 这对它的工作非常重要。 - ree6
我以前从未接触过ReactiveUI...但它看起来相当奇怪。遵循MVVM模式,没有必要将代码放在代码后置文件中。我不建议在代码后置中手动绑定所有属性,因为你可以在xaml标记中轻松实现这一点。在我看来,这似乎是一种反模式 :/ - Sandman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接