Changing content of GroupBox based on ComboBox selection - c#

I have a ComboBox containing four int values, 1, 2, 3, 4. Below that, I have a GroupBox. What I'm trying to do is display unique content in the GroupBox based on the values of the ComboBox; i.e. maybe a TextBlock or TextBox for 1 and a Button for 2.
Currently, I have four separate GroupBoxes all on top of each other and have the Visibility property of the those GroupBoxes bound to unique bool properties in the ViewModel which I set whenever the SelectedItem of the ComboBox changes. But I feel like there's a way where I can condense this and just keep one GroupBox and fill it with the correct content instead of having four separate ones and four separate IsVisible properties.
<ComboBox ItemsSource="{Binding PackageTypes}" SelectedItem="{Binding SelectedType}" />
<GroupBox /><!--Change content of this based on combobox's selection-->
public class MainViewModel : ViewModelBase
{
private PackageModel_selectedPackageModel;
public PackageModel SelectedPackageModel
{
get => _selectedPackageModel;
set
{
_selectedPackageModel = value;
}
}
public ObservableCollection<int> PackageTypes { get; set; }
public int SelectedType { get; set; }
public MainViewModel()
{
PackageTypes = new ObservableCollection<int>() { 1, 2, 3, 4 };
}
}

You may use a Style with triggers, e.g.:
<ComboBox x:Name="cmb" xmlns:s="clr-namespace:System;assembly=mscorlib">
<s:Int32>1</s:Int32>
<s:Int32>2</s:Int32>
<s:Int32>3</s:Int32>
<s:Int32>4</s:Int32>
</ComboBox>
<GroupBox Header="...">
<GroupBox.Style>
<Style TargetType="GroupBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=cmb}" Value="1">
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="1..." />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem, ElementName=cmb}" Value="2">
<Setter Property="Content">
<Setter.Value>
<Button Content="2..." />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
</GroupBox>
Instead of binding to the SelectedItem property of the ComboBox, you may of course bind to a source property of the view model:
<DataTrigger Binding="{Binding SelectedType}" Value="1">
Make sure that the view model implements the INotifyPropertyChanged interface and raises change notifications.

Related

Style SelectedItem in ListBox upon Load WPF

I have a ListBox, as such:
<ListBox
//other stuff
ItemsSource="{Binding ViewModels, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedThing, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Purple" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ViewModel.cs:
public SomeType SelectedThing
{
get => selectedThing;
set => //set with INotifyPropertyChanged
}
public ObservableCollection<SomeType> ViewModels
{
get => viewModels;
set => //set with INotifyPropertyChanged
}
It's possible that SelectedThing is defined in the ViewModel when loading the app, so I want it to be styled accordingly.
It works great when I open the app and then click on an item, but how can the style be applied on load?
I tried with:
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource Mode=Self}}" Value="True">
<Setter Property="Background" Value="Purple" />
</DataTrigger>
</Style.Triggers>
But every item of the ListBox is enabled, so it applies it to every item upon load.
EDIT:
After debugging a bit, I found out that when setting SelectedThing on load, SelectedItem remains null.
EDIT:
Here is the OnLoaded method in the ViewModel, where I am setting SelectedThing if the user has selected it while previously using the app. The purpose is to keep the selection after closing and reopening the app.
public IAsyncRelayCommand OnLoadedCommand { get; set; }
In the constructor:
OnLoadedCommand = new AsyncRelayCommand(OnLoaded);
In the View:
<b:Interaction.Triggers>
<b:EventTrigger EventName="Loaded">
<b:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
The actual method:
public async Task OnLoaded()
{
//other stuff
if (App.Current.Properties.Contains(nameof(SelectedThing)))
{
var selected = JsonConvert.DeserializeObject<SomeType>(App.Current.Properties[nameof(SelectedThing)].ToString());
SelectedThing = selected;
}
}
The reason why there is no item selected is because you are setting the SelectedThing source property to a value that's not in the ViewModels source collection.
As you have already discovered, this works better:
var selectedViewModel = ViewModels.Where(x => x.SelectedThing == selected.SelectedThing).FirstOrDefault();
SelectedThing = selectedViewModel;
The item to be selected must be present in the source collection.

How do I bind a WPF combo box to a different list when the dropdown is open?

I have several combo boxes in a Scheduling module that all have dropdown lists based on an "Active" field.
public class Project
{
public int ProjectID { get; set; }
public int ProjectTitle { get; set; }
public bool Active { get; set; }
}
<ComboBox
Name="ProjectComboBox"
ItemsSource="{Binding AllProjects}"
SelectedItem="{Binding Project, Mode=TwoWay}">
</ComboBox>
The calendar's editing form must always display legacy information in its combo boxes, even if a particular item in a combo list has been deactivated. But if the drop-down is opened, it must only show those items in the list that are still active.
How would I accomplish this?
I have tried this, in the codebehind:
private void ProjectComboBox_DropDownOpened(object sender, EventArgs e)
{
ProjectComboBox.SetBinding(ItemsControl.ItemsSourceProperty, "ActiveProjects");
}
private void ProjectComboBox_DropDownClosed(object sender, EventArgs e)
{
ProjectComboBox.SetBinding(ItemsControl.ItemsSourceProperty, "AllProjects");
}
Which displays the correct list in the dropdown, but de-selects the originally-selected Project. If the user does not select a new project, the combo box needs to retain its original selection when the dropdown is closed.
instead of changing ItemsSource, hide inactive elements via Visibility binding:
<BooleanToVisibilityConverter x:Key="boolToVisibility"/>
<ComboBox Name="ProjectComboBox"
ItemsSource="{Binding AllProjects}"
DisplayMemberPath="ProjectTitle"
SelectedItem="{Binding Project, Mode=TwoWay}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Visibility"
Value="{Binding Active, Converter={StaticResource boolToVisibility}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
This also works, and might provide better flexibility for those looking to do something similar:
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Active}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>

WPF Using bindings to ViewModel in style used to change controls

The objective is to swap the control at runtime based on a property in my ViewModel, and have the displayed control be have a binding that updates the properties in the ViewModel. I started by creating the following Style in the View.xaml:
<UserControl.Resources>
<Style x:Key="DisplayTextOrButton" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding TextNotButton}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Content="{Binding SomeText}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding TextNotButton}" Value="False">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="{Binding ButtonText}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
Note that the bindings LabelText and ButtonText are the bindings to the properties in the ViewModel.
Then further on in the View.xaml I have the following:
<ContentControl Content="{Binding TextNotButton}"
Style="{StaticResource DisplayTextOrButton}">
</ContentControl>
Finally, the ViewModel.cs has the following properties:
private bool textNotButton;
public bool TextNotButton
{
get => this.textNotButton;
set
{
this.textNoButton = value;
this.OnPropertyChanged("TextNotButton");
}
}
private string someText;
public string SomeText
{
get => this.someText;
set
{
this.someText = value;
this.OnPropertyChanged("SomeText");
}
}
private string buttonText;
public string ButtonText
{
get => this.buttonText;
set
{
this.buttonText = value;
this.OnPropertyChanged("ButtonText");
}
}
The style works well for swapping between the label and the button, but changing the text in the TextBox does not update the property in the ViewModel, and the Button's text is empty (I imagine because the binding hasn't worked)
I believe this is because the style is a static resource so the bindings SomeText and ButtonText in the style aren't actually the bindings in the ViewModel, but I'm not sure how to pass the reference of the other properties into the style. Or even if that's a thing. I'm pretty new to XAML so not sure on how to handle this

ComboBox header not respecting ItemTemplate

In a WPF project I have a ComboBox where the DataTemplate used for the ItemTemplate changes the Background colour of a Border based on the IsSelected property of the Person object that the ComboBoxItem's are bound to. So, in my example below, when IsSelected=true the Background=LightGreen.
All this is fine when the ComboBox's dropdown is open. However, when the dropdown closes after selecting an item with Background=LightGreen, the ComboBox's header doesn't show the LightGreen colour.
What do I need to do to show the LightGreen colour once the ComboBox has closed up on an IsSelected=true item?
Here is some example code to show what I mean.
XAML:
<Window x:Class="combo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:combo"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding .}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Border HorizontalAlignment="Stretch">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}, Path=DataContext.IsSelected}" Value="True">
<Setter Property="Background" Value="LightGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Email}">
</TextBlock>
</StackPanel>
</Border>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Person[]
{
new Person() { Name = "Mickey" , Email= "m#disney.com" , IsSelected = false},
new Person() { Name = "Donald" , Email= "d#disney.com", IsSelected = true },
new Person() { Name = "Pluto" , Email= "p#disney.com", IsSelected = false }
};
}
}
public class Person
{
public string Name { get; set; }
public string Email { get; set; }
public bool IsSelected { get; set; }
}
The RelativeSource in your trigger looks for a ComboBoxItem, which you’ll only find in the ItemsPresenter inside the pop up the ComboBox.
When the pop up is closed, what we see is a ToggleButton and a ContentPresenter.
In case the marking didn’t give it away:
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=Content.IsSelected, RelativeSource={RelativeSource
AncestorType=ContentPresenter}}" Value="True">
<Setter Property="Background" Value="LightGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
I'm unsure about if you're talking about the way the ComboBox looks when closed, or if you are talking about the dropdown items background color not updating.
This answer already provides a solution for the first, but if you're wondering why the background color isn't updating on the selected item, it's because you are not binding ComboBoxItem.IsSelected to Person.IsSelected anywhere, so they are not synchronized.
Here's an example of how do add that binding :
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}" >
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ComboBox.Resources>
That said, you may still have issues because you are not setting the default selected item. Typically when wanting to provide for single selection capabilitilikethis, I see this done more in the style of
ObservableCollection<Person> People { get; set; }
Person SelectedPerson { get; set; }
with XAML being
<ComboBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}" />
This way, you don't need to write any sync code to bind ComboBoxItem.IsSelected to your Person.IsSelected.

How to visually show multiselect on TreeView in WPF

I'm attempting to enable a TreeView control to support multi select.
The very basic flow works, if you select multiple items in the TreeView while holding down ctrl or shift then it will successfully add those items to a list I have in the view model.
The problem is that when actually clicking on the TreeView items it will only select one visually i.e. only one item is marked as selected. How can I make it highlight/mark multiple items? I don't understand where this is controlled.
The TreeView xaml:
<TreeView x:Name="availableColumnsTreeView"
AutomationProperties.AutomationId="availableColumnsTreeView"
x:Uid="availableColumnsTreeView"
SelectedItemChanged="availableColumnsTreeView_SelectedItemChanged"
ItemsSource="{Binding Path=TreeFieldData, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate x:Uid="HierarchicalDataTemplate_1" ItemsSource="{Binding Path=Children, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">
<TextBlock x:Uid="TextBlock_1" Text="{Binding DisplayName.Text, Mode=OneWay}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So "availableColumnsTreeView_SelectedItemChanged" is invoked fine, but I need it to actually highlight the selected items.
EDIT: Please read my question before marking it as a duplicate. I tried to be as specific as possible to what my problem is. I'm not looking for a whole solution for multi select hidden away in some one drive document.
I'm not sure if I 100% follow you. Could provide a small example please?
Sure.
Here is xaml:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children, Mode=OneWay}">
<CheckBox Content="{Binding Text, Mode=OneWay}" IsChecked="{Binding IsSelected}">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<DataTrigger.Setters>
<Setter Property="Foreground" Value="Red" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And here is cs:
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Text { get; set; }
public List<Item> Children { get; set; }
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
}
}
public Item(string text)
{
Text = text;
}
}
public partial class MainWindow : Window
{
public List<Item> Items { get; set; } = new List<Item>
{
new Item("1") { Children = new List<Item>
{
new Item("11"),
new Item("12"),
new Item("13"),
}},
new Item("2") { Children = new List<Item>
{
new Item("11"),
new Item("12"),
new Item("13"),
}},
new Item("3"),
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
I am using CheckBox to select item (no idea how you do it). If item is selected it has its foreground changed to red via data trigger.
As you can see selection (disregards how you implement it, really, I am using single selection TreeView) is stored inside items as IsSelected value. You can traverse hierarchical collection to get a list of selected items (this is called flattering).
Note: IPropertyChanged, it's required if you plan to set IsSelected from code-behind (e.g. select all items on button press).
It should be easy to adapt to your case.
How can I make it highlight/mark multiple items? I don't understand where this is controlled.
You define the appearance of a TreeViewItem container using a TreeViewItem style. If you add an "IsSelected" property to your data object that keeps track of whether the item is currently selected, you could use a DataTrigger that binds to this one and provide the highlighting, e.g.:
<TreeView>
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
...
</TreeView>
Make sure that the data class where the "IsSelected" property is defined implements the INotifyPropertyChanged interface and that you set this property in your event handler or command.

Categories

Resources