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");
}
}
Related
I have the next classes:
public class TestData
{
public int Id { get; set; }
public Calibration NewCalibration { get; set; }
public Calibration OldCalibration { get; set; }
}
public class Calibration
{
public string Name { get; set; }
public Image Image { get; set; }
}
I have a collection of TestData objects. Each object has 2 types of calibration.
I bind this collection in some window control to the ItemSource of the custom DataGrid control.
In the file of custom DataGrid control, I want to create the DataTemplate for the DataGridCell which will show the picture.
<DataTemplate x:Key="NewSuccess">
<Border Padding="2">
<Image Source="{Binding NewCalibration.Image, Mode=OneWay}"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="OldSuccess">
<Border Padding="2">
<Image Source="{Binding OldCalibration.Image, Mode=OneWay}"/>
</Border>
</DataTemplate>
There is some Style where I want to change the template by some Triggers:
<Style x:Key="TestStyle" TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding SomeValue} Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource NewSuccess}"/>
</DataTrigger>
<DataTrigger Binding="{Binding SomeValue} Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource OldSuccess}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Also, I create some test DataTemplate which doesn't have binding to some data. It just contains the TextBlock with static text.
<DataTemplate x:Key="TestTemplate">
<Border Padding="2">
<TextBlock Text="Test"/>
</Border>
</DataTemplate>
When I set the TestStyle to the DataGridTemplateColumn.CellStyle I don't see any of pictures described in the NewSuccess or OldSuccess template. But when I use TestTemplate in this style - I see the "Test" text. property - all works, I see the "Test" text. But when I try to set the NewSuccess or OldSuccess template - I don't see anything. It seems that it can't bind to the NewCalibration.Image or OldCalibration.Image property.
I also added RelativeSource to the binding, but it didn't help.
How can I fix this problem?
I fixed the problem by adding to the Binding "DataContext" and FindAncestor by the DataGridCell type:
<DataTrigger Binding="{Binding DataContext.SomeValue, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell}}" Value="True">
Hell all. I work on some desktop application that contains some UI windows created by WPF. One of these windows has a table. Each of the headers in this table can open our custom control for filtering. This custom control contains the ListView element with binding to some collection of items. And when I change the state of some items in this collection the scroll bar in the ListView scrolling incorrectly.
Here is the class that I use to represent the info about each item in the ListView:
public class ItemInfo : ViewModelBase
{
public bool IsSelected
{
get => isSelected;
set
{
Set(ref isSelected, value);
}
}
public bool IsAvailable
{
get => isAvailable;
set
{
Set(ref isAvailable, value);
}
}
public string Name
{
get => name;
set
{
Set(ref name, value);
}
}
private string name;
private bool isSelected = true;
private bool isAvailable = true;
}
Each of the items in the ListView contains the checkbox. The IsSelected property indicates the state of this checkbox. The IsAvailable property indicates if the item in the ListView will be visible or hidden.
Here is the code for the ListView:
<ListView ItemsSource="{Binding CollectionForFiltering}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsAvailable}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox IsChecked="{Binding IsSelected}">
<CheckBox.Content>
<Label Content="{Binding Name}"/>
</CheckBox.Content>
</CheckBox>
</HierarchicalDataTemplate>
</ListView.ItemTemplate>
</ListView>
The CollectionForFiltering is described as:
List<ItemInfo> items = new List<ItemInfo>(... some collection of items ...);
ICollectionView CollectionForFiltering = CollectionViewSource.GetDefaultView(items);
In some cases, I change the IsAvailable property for some items in the collection. And these items will be hidden in the ListView. But when I try to scroll the list I see some changes in the scrollbar size during the scrolling. Please, see the attached gif:
How can I fix that? How can I refresh/update the scrollbar after some items are hidden?
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);
}
}
...
}
I have a question about a listview setup like the example below. When I click the button below in the expander header I want that item to be selected as well, but what I'm seeing is while the button command does work, the item selected is still the previous item selected, not the item my button is in. How can I have the Item selected when the button is clicked?
I tried setting up a ControlTemplate like this, but it did not work.
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ListView ItemsSource="{Binding MyItemSource,
Mode=TwoWay}"
SelectedItem="{Binding MySelectedItem,
Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}, Path=IsSelected}">
<Expander.Header>
<Button Command={Binding MyCommand}>Click Me</Button>
</Expander.Header>
<!-- content here -->
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I would suggest defining a command SelectItem in the main ViewModel which takes the item which is to be selected as a parameter. The execution method of this command can then set the MySelectedItem property, set a property IsSelected on the item ViewModel to true and invoke all further actions on the item itself (i.e. what is now executed by MyCommand). With the selection logic in the ViewModel and a clean binding you don't even need to use ListView at all but can stick to a plain ItemsControl:
The XAML then looks like this:
<ItemsControl ItemsSource="{Binding MyItemSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="{Binding IsSelected}">
<Expander.Header>
<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}, Path=DataContext.SelectItem}"
CommandParameter="{Binding"}>Click Me</Button>
</Expander.Header>
<!-- content here -->
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The MainViewModel would look something like this:
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<ItemViewModel> MyItemsSource { get; private set; }
public ItemViewModel SelectedItem { get... set... }
public ICommand SelectItem { get; private set; }
ctor()...
private void ExecuteSelectItem(ItemViewModel item)
{
SelectedItem = item;
foreach (var i in MyItemsSource) i.IsSelected = false;
item.IsSelected = true;
item.DoSomething();
}
}
I have always found it way easier to use ItemsControl an implement the few lines of selection logic myself, instead of dealing with the messy binding of the selection of a ListView. In my opinion it is a quite intuitive to implement custom selection behavior (multiple items, allowing only certain combinations, etc.). You can use the IsSelected property easily to apply a custom styling of selected items.
You can try something like this in your view model, add if statement in setter:
private object _mySelectedItem;
public object MySelectedItem
{
get { return _mySelectedItem; }
set
{
if (_mySelectedItem != value && value != null)
{
_mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
}
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}" />