Change ComboBox ItemsSource based on another ComboBox selection - c#

I have two ComboBoxesA and B. First one is populated with a list of items.
I need to change the ItemsSource Binding of the B based on A's SelectedItem
Issue: When X is selected on A, B is not getting populated.
Please note that I'm not filtering the ItemsSource, it a complete change of binding. myItemSources are ObservableCollections
<ComboBox Name="ComboBoxB">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding myItemSource1}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedValue, ElementName=ComboBoxA}" Value="X">
<Setter Property="ItemsSource" Value="{Binding myItemSource2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>

There is an example of how to implement this kind of cascading ComboBoxes using the MVVM design pattern available here: https://blog.magnusmontin.net/2013/06/17/cascading-comboboxes-in-wpf-using-mvvm/
<ComboBox ItemsSource="{Binding Countries}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCountry}" />
<ComboBox ItemsSource="{Binding Cities}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCity}"
Margin="0 5 0 0"/>
public CustomObservableCollection<City> Cities {
get;
private set;
}
private Country _selectedCountry;
public Country SelectedCountry {
get {
return _selectedCountry;
}
set {
_selectedCountry = value;
this.Cities.Repopulate(_selectedCountry.Cities);
}
}
...
}

Related

C# WPF DataGrid: DataGridComboxBoxColumn Binding ItemsSource failed

i want to display a ComboBox on my DataGrid. But the ComboBox does not load the ObservableCollection. My ObservableCollection 'Projects' is on my ViewModel defined. The problem is not the DataContext. But when I define the ComboBox outside of my DataGrid, the binding works. Does anyone have an idea where my problem is?
ViewModel:
public Project SelectedProject
{
get { return _project; }
set
{
if (_project != value)
{
_project = value;
OnPropertyChanged();
_actions = _database.LoadActions(SelectedProject.Id);
OnPropertyChanged(() => Actions);
}
}
}
public ObservableCollection<Project> Projects
{
get { return _database.LoadProjects(); }
}
XAML:
<DataGridComboBoxColumn Header="Projekt:" Width="140" DisplayMemberPath="Name">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Projects, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="SelectedItem" Value="{Binding SelectedProject, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Projects, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="SelectedItem" Value="{Binding SelectedProject, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="IsDropDownOpen" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
Try this:
<Setter Property="ItemsSource" Value="{Binding DataContext.Projects, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
The DataContext of the ComboBox in a DataGridComboBoxColumn is the corresponding object in the DataGrid's ItemsSource.
If your Projects collection is defined in the view model, you need to specify a RelativeSource for the binding to work.

ListViewItem change text color using index

I want to create a playlist.
But I have a problem to highlight the selected item indicated by the user and change the background color or the color of the text item that is played now.
Properties i want to show - what is play now (int IndexToPlay) what user selects (SelectedIndex)
The user should select only one item
I use WPF, MVVM, and I read a lot of posts, but unfortunately I have not found a solution.
<ListView Name="List"
Grid.ColumnSpan="11" Margin="7,0,7,0.4" Grid.Row="9"
ItemsSource="{Binding MusicList, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedIndex}"
>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If you have IsSelected and IsPlaying properties in your ViewModel class:
public class MusicItem
{
private bool _isPlaying;
public bool IsPlaying
{
get
{
return _isPlaying;
}
set
{
_isPlaying = value;
OnPropertyChanged();
}
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
You can set the ItemContainerStyle to bind the IsSelected property and then you can use a style on the ItemTemplate with a DataTrigger to change the appearance of the ItemTemplate:
<ListView Name="List"
ItemsSource="{Binding MusicList, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedIndex}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsPlaying}" Value="True">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

How to make TreeView node selected/expanded when ItemsSource = custom object collection?

I have a TreeView intended to operate on two levels. For his I have two HierarchicalDataTemplate and two custom types. The ItemsSource is linked to a ObservableCollection and everything works fine. I just can't figure how to make a node selected or expanded from codebehind. Somewhere was mentioned a very good idea of binding IsExpanded and IsSelected properties to the corresponding properties in my custom types. The only problem is that the HierarchicalDataTemplate does not implement a TreeViewItem directly, so how can I access these properties in the following code?
<TreeView Name="treeViewNotes" AllowDrop="True" PreviewMouseLeftButtonDown="treeViewNotes_PreviewMouseLeftButtonDown" PreviewMouseMove="treeViewNotes_PreviewMouseMove" Drop="treeViewNotes_Drop" DragEnter="treeViewNotes_DragEnter">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type dataclasses:NoteFolder}" ItemsSource="{Binding Notes}">
<StackPanel Orientation="Horizontal">
<Image Height="16" Source="{Binding TreeViewIcon}" Tag="{Binding Self}"/>
<TextBlock Text="{Binding Title}" Tag="{Binding Self}" Margin="3"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataclasses:Note}">
<StackPanel Orientation="Horizontal">
<Image Height="16" Source="{Binding TreeViewIcon}" Tag="{Binding Self}"/>
<TextBlock Text="{Binding Title}" Tag="{Binding Self}" Margin="3"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
My goal is when creating and adding a new Note to some NoteFolder, to make this Note selected and the Folder expanded. The same would be needed to further improve the UI response on drag&drop.
You could try changing the TreeView's ItemContainerStyle in the following way, so that its IsExpanded and IsSelected properties would bind to the DataContext's IsExpanded and IsSelected:
<TreeView x:Name="..." ItemsSource="{Binding RootNode}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- Items in the ItemsSource need to have these properties for the binding to work -->
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- You can also optionally change some style values based on IsSelected and IsExpanded values -->
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="BorderThickness" Value="4 0 0 1"/>
<Setter Property="BorderBrush" Value="DeepSkyBlue"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected}" Value="False">
<Setter Property="BorderThickness" Value="4 0 0 1 "/>
<Setter Property="BorderBrush" Value="Transparent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate>
...
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Of course, then each item coming from the ItemSource and present in the hierarchy needs to have these properties.
The only problem is that the HierarchicalDataTemplate does not
implement a TreeViewItem directly, so how can I access these
properties in the following code?
You can do that in TreeView.ItemContainerStyle:
<TreeView ItemTemplate="{StaticResource ResourceKey=treeViewDataTemplate}"
ItemsSource="{Binding Data}"
Name="trvTreeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" />
<EventSetter Event="Expanded" Handler="TreeViewItem_Expanded" />
<EventSetter Event="Collapsed" Handler="TreeViewItem_Collapsed" />
<EventSetter Event="PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
In the code behind:
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItem = e.OriginalSource as TreeViewItem;
if (treeViewItem != null)
{
BaseObjectExplorerNode baseObjectExplorerNode = treeViewItem.Header as BaseObjectExplorerNode;
if (baseObjectExplorerNode != null)
{
baseObjectExplorerNode.IsExpanded = true;
}
}
}
private void TreeViewItem_Collapsed(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItem = e.OriginalSource as TreeViewItem;
if (treeViewItem != null)
{
BaseObjectExplorerNode baseObjectExplorerNode = treeViewItem.Header as BaseObjectExplorerNode;
if (baseObjectExplorerNode != null)
{
baseObjectExplorerNode.IsExpanded = false;
}
}
}
And then for example:
root.IsExpanded = true;
I don't see any problem. You can make a StyleSelector, two Style's and bind to properties.
XAML:
<TreeView Name="treeViewNotes" AllowDrop="True" PreviewMouseLeftButtonDown="treeViewNotes_PreviewMouseLeftButtonDown" PreviewMouseMove="treeViewNotes_PreviewMouseMove" Drop="treeViewNotes_Drop" DragEnter="treeViewNotes_DragEnter">
<TreeView.Resources>
<!-- StyleSelector for containers -->
<notdataclasses:NoteStyleSelector x:Key="NoteStyleSelector" />
<!-- Style for a NoteFolder's container -->
<Style x:Key="NoteFolderStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" />
<Setter Property="ItemContainerStyleSelector" Value="{StaticResource NoteFolderStyle}" />
</Style>
<!-- Style for a Note's container -->
<Style x:Key="NoteStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
</Style>
<!-- ... -->
</TreeView.Resources>
<TreeView.ItemContainerStyleSelector>
<StaticResource ResourceKey="NoteStyleSelector" />
</TreeView.ItemContainerStyleSelector>
</TreeView>
NoteStyleSelector:
public sealed class NoteStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
FrameworkElement fe = container as FrameworkElement;
if (fe!= null)
{
if (item is Note)
{ return (Style)fe.FindResource("NoteStyle"); }
if (item is NoteFolder)
{ return (Style)fe.FindResource("NoteFolderStyle"); }
}
return base.SelectStyle(item, container);
}
}
In a drop handler:
currentFolder.Nodes.Add(pastedNode);
currentFolder.IsExpanded = true;
currentNode.IsSelected = true;
Data casses:
public class Note : INotifyPropertyChanged
{
// Only the IsSelected property because a Note can not be expanded
public bool IsSelected { get { /* ... */ } set { /* ... */ } }
}
public class NoteFolder : INotifyPropertyChanged
{
public bool IsSelected { get { /* ... */ } set { /* ... */ } }
public bool IsExpanded { get { /* ... */ } set { /* ... */ } }
}

SelectedIndex DataTrigger not working in Combobox

What I want to do is if a combobox has only 1 item then it gets preselected. I tried usind DataTriggers but its not working for 'SelectedIndex' property.But when I set 'IsEnabled' property to false its working and disable the combobox.
Below is my code:
<ComboBox Name="WarehouseCombo"
ItemsSource="{Binding Path=WarehouseList}"
SelectedValue="{Binding Warehouse,Mode=TwoWay}"
Text="{Binding Path=TxtWarehouse,Mode=TwoWay}">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=Items.Count, ElementName=WarehouseCombo}" Value="1">
<Setter Property="SelectedIndex" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
Please help me as why is this happening in 'SelectedIndex' case.
Don't use the SelectedIndex property. Instead, use the SelectedItem property. One way to fulfil your requirements is to declare a base class for your data collection. Try something like this:
public WarehouseList : ObservableCollection<WarehouseItems>
{
private T currentItem;
public WarehouseList() { }
public T CurrentItem { get; set; } // Implement INotifyPropertyChanged here
public new void Add(T item)
{
base.Add(item);
if (Count == 1) CurrentItem = item;
}
}
Now if you use this class instead of a standard collection, it will automatically set the first item as selected. To make this work in the UI, you simply need to data bind this property to the SelectedItem property like this:
<DataGrid ItemsSource="{Binding WarehouseList}"
SelectedItem="{Binding WarehouseList.CurrentItem}" />

WPF switch binding with trigger

I'm trying to switch the ItemsSource property on a ComboBox via triggers on a checkbox. Here is my code:
<CheckBox Content="Test" VerticalAlignment="Center" Margin="5,0,0,0">
<CheckBox.Triggers>
<Trigger Property="CheckBox.IsChecked" Value="True">
<Setter TargetName="MyComboBox" Property="ComboBox.ItemsSource" Value="{Binding A}" />
</Trigger>
<Trigger Property="CheckBox.IsChecked" Value="False">
<Setter TargetName="MyComboBox" Property="ComboBox.ItemsSource" Value="{Binding B}" />
</Trigger>
</CheckBox.Triggers>
</CheckBox>
As you can see, the intended purpose is to switch between binding "A" and binding "B" depending on the checkbox's IsChecked state. I've seen a lot of people put these triggers in a Style, but that gets rid of my window theme, which I want to keep. Additionally, I'd like this to be in XAML only as I need to apply this sort of binding switch to multiple combo box / checkbox pairs in my application.
The problem I'm having is that when I put in the above code my application crashes on startup! I've isolated it to the trigger code above (removing that removes the crash). Any help is appreciated!
I'm guessing that MyComboBox is not contained in the CheckBox and is therefore out of the naming scope that the Trigger is defined.
Instead of adding the trigger to the CheckBox, why not add it to the ComboBox and binding the CheckBox.IsChecked property to a property in your view model, like so:
<CheckBox IsChecked="{Binding ShowComboBoxItemsA}"/>
<ComboBox ItemsSource="{Binding A}">
<ComboBox.Triggers>
<DataTrigger Binding="{Binding ShowAComboBoxItems}" Value="False">
<Setter Property="ItemsSource" Value="{Binding B}"/>
</DataTrigger>
</ComboBox.Triggers>
</ComboBox>
The other option would be bind the CheckBox.IsChecked property to a property in your view model, as in the first, but then in your setter update the value of the ComboBoxItems.
<CheckBox IsChecked="{Binding ShowComboBoxItemsA}"/>
<ComboBox ItemsSource="{Binding ComboBoxItems}"/>
public List<object> ItemsA { get; set; }
public List<object> ItemsB { get; set; }
bool showComboBoxItemsA;
public bool ShowComboBoxItemsA
{
get { return showComboBoxItemsA; }
set
{
if (showComboBoxItemsA != value)
{
showComboBoxItemsA = value;
OnPropertyChanged("ShowComboBoxItemsA");
if (showComboBoxItemsA)
ComboBoxItems = ItemsA;
else
ComboBoxItems = ItemsB;
}
}
}
List<object> comboBoxItems;
public List<object> ComboBoxItems
{
get { return comboBoxItems; }
set
{
comboBoxItems = value;
OnPropertyChanged("ComboBoxItems");
}
}

Categories