I have this model:
public class Option : BindableBase
{
private bool _enabled;
public bool Enabled
{
get
{
return _enabled;
}
set
{
SetProperty(ref _enabled, value);
}
}
private string _value;
public string Value
{
get
{
return _value;
}
set
{
SetProperty(ref _value, value);
}
}
}
In my viewmodel, I got a list Options of type ObservableCollection<Option>.
I use this XAML piece of code:
<ComboBox Width="200"
ItemsSource="{Binding Options}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}"/>
<TextBlock Text="{Binding Enabled}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding Enabled}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
I add some Option in my list, some of them have the Enabled property set to true while others do not.
However, the ones having the property to false are still enabled in the ComboBox and I can select them (while I should not!). When using this line of code:
<Setter Property="IsEnabled" Value="false"/>
The options are effectively disabled (I can't select any of them) but I'd like to use some binding. Can someone explain what I'm doing wrong here?
You are setting IsEnabled on the content of your ComboBoxItem while you would need to set it on the ComboBoxItem itself. You would need to set the binding in the ItemContainerStyle. I'm not sure if bindings are supported in style setters though. If they are not, then you might need to use a workaround to set up the binding.
Maybe you need to use path instead. You can try this :
<Setter Property="IsEnabled" Value="{Binding Path=Enabled}"/>
Related
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 the following custom control based on the "heavy option" at this link:
public partial class SelectableContentControl : ContentControl
{
public SelectableContentControl()
{
InitializeComponent();
var isCheckedDesc = DependencyPropertyDescriptor.FromProperty(IsCheckedProperty, typeof(SelectableContentControl));
isCheckedDesc.AddValueChanged(this, IsCheckedPropertyChanged);
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool),
typeof(SelectableContentControl), new PropertyMetadata(false));
private void IsCheckedPropertyChanged(object sender, EventArgs e)
{
var selectable = Content as IAmSelectable;
if (selectable != null) selectable.IsSelected = IsChecked;
}
}
The style defined for the SelectableContentControl is as follows:
<Style TargetType="{x:Type controls1:SelectableContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{TemplateBinding IsChecked}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and my usage:
<controls:SelectableContentControl Grid.Row="2" Content="{Binding Dummy}" IsChecked="{Binding Dummy.IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I want IsCheckedPropertyChanged to be called whenever the IsChecked value changes on the UI, but this isn't happening. Anyone see what I'm missing?
TemplateBinding works in a OneWay mode, meaning that the value is updated only in source-to-target direction (your control being the source, and the CheckBox inside the template the target). If you want the binding to work in TwoWay mode, you should use a normal Binding instead:
<ControlTemplate TargetType="{x:Type controls1:SelectableContentControl}">
<CheckBox IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
Note that you don't need to specify Mode=TwoWay on the binding, because CheckBox.IsChecked property binds in two-way mode by default.
See this question for more detailed info.
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}" />
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");
}
}
I try to simply databind IsSelected property with IsSelected field in my class. But after I change the value in code, it doesn't change the property, neither does clicking on ListBoxItem change the field value.
XAML:
<FlipView ItemsSource="{Binding Source={StaticResource itemsViewSource}}" ... >
<FlipView.ItemTemplate>
<DataTemplate>
<UserControl Loaded="StartLayoutUpdates"
Unloaded="StopLayoutUpdates">
<!-- other controls -->
<ListBox Grid.Row="1" Grid.ColumnSpan="3"
SelectionMode="Multiple" VerticalAlignment="Center"
ItemsSource="{Binding Answers}">
<ListBox.Resources>
<local:LogicToText x:Key="logToText" />
</ListBox.Resources>
<!-- bind IsSelected only in one way from
code to content -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBoxItem
IsSelected="{Binding IsSelected, Mode=TwoWay, Converter={StaticResource logToText}}"
Content="{Binding IsSelected, Mode=TwoWay, Converter={StaticResource logToText}}">
</ListBoxItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- not working at all
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="Content"
Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>-->
</ListBox>
</UserControl>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Code:
Answers
private ObservableCollection<PrawoJazdyDataAnswer> _answers =
new ObservableCollection<PrawoJazdyDataAnswer>();
public ObservableCollection<PrawoJazdyDataAnswer> Answers
{
get
{
return this._answers;
}
}
Single item(Answer)
public class PrawoJazdyDataAnswer : NPCHelper// PrawoJazdy.Common.BindableBase
{
public PrawoJazdyDataAnswer(String ans, bool ansb)
{
this._ans = ans;
this._isSelected = ansb;
}
public override string ToString()
{
return _isSelected.ToString(); //Only For debug purposes
//normally return _ans
}
private string _ans;
public string Ans
{
get { return this._ans; }
//set { this.SetProperty(ref this._ans, value); }
}
private bool _isSelected;
public bool IsSelected
{
get { return this._isSelected; }
set
{
_isSelected = value;
FirePropertyChanged("IsSelected");
//this.SetProperty(ref this._isSelected, value);
}
}
}
FirePropertyChanged
public class NPCHelper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Converter(which sometimes seems to be needed and others not..., I tried ~10 approaches from different tutorials/examples)
public class LogicToText : IValueConverter
{
/// <summary>
///
/// </summary>
public object Convert(object value, Type targetType,
object parameter, string language)
{
//if (value == null || (bool)value == false)
// return "False";
return value.ToString();
}
/// <summary>
///
/// </summary>
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
return value.ToString().Contains("True") ? true : false;
}
Thanks in advance, and sorry for my English(still learning).
#edit
Thanks for quick reply.
For test purposes I created a button and text block:
<Button Click="spr" >Sprawdź</Button>
<TextBlock Text="{Binding Answers[0].IsSelected, Mode=TwoWay}" > </TextBlock>
It's in other controls part (above list box, but in FlipView)
Click method
private void spr(object sender, RoutedEventArgs e)
{
var ans = ((PrawoJazdyDataQuestion)this.flipView.SelectedItem).Answers;
foreach (var item in ans)
item.IsSelected = item.IsSelected ? false : true;
}
As I wrote, when i'm changing data from code side, it is changing content of element, but not appearance of ListBoxItem. And if I just select it on ListBox, it doesn't change the data in TextBlock neither in ListBox itself.
#edit2 fixing typos...
To change the IsSelected property of the ListBoxItem, you need to change the ListBox.ItemContainerStyle. See here:
<ListBox Grid.Row="1" Grid.ColumnSpan="3"
SelectionMode="Multiple" VerticalAlignment="Center"
ItemsSource="{Binding Answers}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding IsSelected}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since the binding mode is TwoWay, selecting and deselecting the ListBoxItem's dynamically changes the content of the items to display either True or False.
Also notice how I changed the ListBox.ItemTemplate into a TextBlock instead of a ListBoxItem. The ItemTemplate defines the content of the ListBoxItem, so some type of content control is typically used. Below is the UI structure for the different layouts (this can be viewed using the WPF Tree Visualizer).
ListBoxItem as the ItemTemplate:
TextBlock as the ItemTemplate:
Edit
Also note that I removed the IValueConverter. Since your source and target properties are both bool, there is no need for a converter in this case. Try removing the converter references to see if that fixes the problem.
Do not bind to IsSelected on ListBoxItem. ListBox got SelectedItem or SelectedItems property, where You should pass item(s) to be selected, i.e. by binding.
Setting IsSelected property in model is not good idea. Better make SelectedItem notify property in ViewModel.