OK so this is definitely a newbie question that unfortunately could not figure/find the answer to.
Essentially binding a list of objects to a Combobox, when the Disabled
property on the object is set to true I want the text colour of the Combobox item to be set to gray.
This is what I have so far:
Combobox item datatype
public class ListItem
{
public ListItem(string text)
{
Text = text;
}
public string Text { get; set; }
public bool Disabled { get; set; }
}
Viewmodel setup
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 Binding
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 markup
<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>
Any help is appreciated let me know if you need more information.
The last item in the dropdown already has its text grayed out, so I assume you're asking about the selected item. The ComboBox
uses separate data templates for the selected item and the items in the dropdown. You can use a DataTemplateSelector
to set both.
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;
}
}
<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>
We have some repetition in the DataTemplate
definitions, but these tend to grow apart in production code.
I'm assuming your problem is that ComboBoxItem
s do not get grayed once the app is running.
I'm not familiar with ReactiveUI , but since I found a problem in your code, I try it in a CommunityToolkit.Mvvm version of your code and verified my theory.
Bottom of line, you need to implement the ReactiveUI version of INotifyPropertyChanged
to the Disabled
property.
If you are interested in, I can post the CommunityToolkit.Mvvm version of this code.
Here is an approach that worked in my tests:
Combobox item datatype
//-- Unchanged
public class ListItem
{
public ListItem( string text )
{
Text = text;
}
public string Text { get; set; }
public bool Disabled { get; set; }
}
Viewmodel setup
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 archieve 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];
}
}
Main page
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 markup
<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>
I hope this will satisfy your requirements. Have fun:)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.