I have a ComboBox with the ItemsSource data bound. This ComboBox also listens to the SelectionChanged event.
However, when the ItemsSource changes, the SelectionChanged event is raised. This happens only the when ItemsSource is a view.
Is there a way to have the SelectionChanged raised only when the user does it, not when the ItemsSource property changes?
If you do your data binding in code behind you can unsubscribe to the SelectionChanged while ItemsSource is being changed. See below sample code:
XAML:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel DataContextChanged="OnDataContextChanged">
<Button Content="Change items" Click="OnClick" />
<ComboBox Name="_cb" />
</StackPanel>
</Window>
Code behind:
public partial class Window1
{
public Window1()
{
InitializeComponent();
_cb.SelectionChanged += OnSelectionChanged;
DataContext = new VM();
}
private void OnClick(object sender, RoutedEventArgs e)
{
(DataContext as VM).UpdateItems();
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
VM vm = DataContext as VM;
if (vm != null)
{
_cb.ItemsSource = vm.Items;
vm.PropertyChanged += OnVMPropertyChanged;
}
else
{
_cb.ItemsSource = null;
}
}
void OnVMPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Items")
{
_cb.SelectionChanged -= OnSelectionChanged;
_cb.ItemsSource = (DataContext as VM).Items;
_cb.SelectionChanged += OnSelectionChanged;
}
}
}
public class VM : INotifyPropertyChanged
{
public VM()
{
UpdateItems();
}
public event PropertyChangedEventHandler PropertyChanged;
private List<string> _items = new List<string>();
public List<string> Items
{
get { return _items; }
set
{
_items = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
}
}
}
public void UpdateItems()
{
List<string> items = new List<string>();
for (int i = 0; i < 10; i++)
{
items.Add(_random.Next().ToString());
}
Items = items;
}
private static Random _random = new Random();
}
I found that method :
private void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(!ComboBox.IsDropDownOpen)
{
return;
}
///your code
}
Related
why PropertyChangedEvent can be add to a String property, but cannot be add to a List<> property?
the xaml likes this:
<StackPanel>
<ListBox ItemsSource="{Binding Items}" Height="300"/>
<Button Click="Button_Click">Change</Button>
</StackPanel>
and the xaml.cs like this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//public ObservableCollection<String> Items { get; set; } = new ObservableCollection<String>(); // ok
public List<String> Items // no effect
{
get { return _items; }
set
{
_items = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
}
}
private List<String> _items = new List<string>();
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Items.Add("aaa");
Items.Add("bbb");
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Items.Clear();
for (int i = 0; i < 5; i++)
Items.Add(i.ToString());
}
}
Your issue can be fixed in multiple ways,
Create another List on Button Click, sample code given below
Use Observable Collection
Change
private RelayCommand _buttonClick;
public RelayCommand ButtonClick => _buttonClick ?? new RelayCommand(onButtonClick, canButtonClick);
private bool canButtonClick(object obj)
{
return true;
}
private void onButtonClick(object obj)
{
List<String> tempItems = new List<string>();
for (int i = 0; i < 5; i++)
tempItems.Add(i.ToString());
Items = tempItems;
}
It doesn't work because you're only notifying when the entire List is set, not when items are added or removed within the list.
Use an ObservableCollection<string> instead of List<string>:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<string> _Items = new ObservableCollection<string>();
public ObservableCollection<string> Items
{
get { return _Items; }
set
{
_Items = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Items));
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Items.Add("aaa");
Items.Add("bbb");
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Items.Clear();
for (int i = 0; i < 5; i++)
Items.Add(i.ToString());
}
}
This is my XAML code:
<Label x:Name="currentPage" Width="45" Height="25" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="{Binding CurrentPageNo, Mode=OneWay}" />
This is my code-behind:
private int currentPageNo;
public int CurrentPageNo
{
get { return currentPageNo; }
set { currentPageNo = value; NotifyPropertyChanged("CurrentPageNo"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void gotoPrevious(object sender, RoutedEventArgs e)
{ this.currentPageNo--;}
private void gotoPrevious(object sender, RoutedEventArgs e)
{ this.currentPageNo++;}
currentPageNo is changed when I press either the next page button or the previous page button, but this doesn't reflect in the UI.
It works when I do this.
private int currentPageNo;
public int CurrentPageNo
{
get { return currentPageNo; }
set { currentPageNo = value; NotifyPropertyChanged("currentPageNo"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void gotoPrevious(object sender, RoutedEventArgs e)
{
this.currentPageNo--;
NotifyPropertyChanged("currentPageNo");
}
private void gotoPrevious(object sender, RoutedEventArgs e)
{
this.currentPageNo++;
NotifyPropertyChanged("currentPageNo");
}
I have to notify from all the places wherever I am changing the value. This doesn't feel right. am I missing something? or it is meant to be done the second way?
You have to notify about a change of the property by using the name of property, not the name of its backing field.
Then you should also increment and decrement the property, not the field.
private int currentPageNo;
public int CurrentPageNo
{
get { return currentPageNo; }
set
{
currentPageNo = value;
NotifyPropertyChanged(nameof(CurrentPageNo)); // property name
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void GotoPrevious(object sender, RoutedEventArgs e)
{
CurrentPageNo--; // set the property
}
private void GotoPrevious(object sender, RoutedEventArgs e)
{
CurrentPageNo++; // set the property
}
An alternative implementation without a backing field could look like shown below. The property setter is private to make sure that only the owning class can set the property. It must then always fire the PropertyChanged event.
public int CurrentPageNo { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void GotoPrevious(object sender, RoutedEventArgs e)
{
CurrentPageNo--;
NotifyPropertyChanged(nameof(CurrentPageNo));
}
private void GotoPrevious(object sender, RoutedEventArgs e)
{
CurrentPageNo++;
NotifyPropertyChanged(nameof(CurrentPageNo));
}
I have a list of user control and each user control have two buttons, and when I click on them, something must happen, but I want to handle this event not inside the user control, I want to handle the events inside the main page
So, How can I catch the events that fired by the selected item user control of list view?
user control code behind:
public sealed partial class TestingUerControl : UserControl
{
public TestingUerControl()
{
this.InitializeComponent();
}
public event EventHandler FirstButtonEvent;
public event EventHandler SecondButtonEvent;
private void firstButton_Click(object sender, RoutedEventArgs e)
{
//Some stuff of code
FirstButtonEvent?.Invoke(this, new EventArgs());
}
private void secondButton_Click(object sender, RoutedEventArgs e)
{
//Some stuff of code
SecondButtonEvent?.Invoke(this, new EventArgs());
}
}
main page xaml markup:
<ListView x:Name="listUserControl"
Width="100"
Header="400">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:MyModel">
<userControl:TestingUerControl/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I used this statement:
((TestingUerControl)listUserControl.SelectedItem).FirstButtonEvent += OnFirstButtonEvent;
but this doesn't work I can cast the SelectedItem to MyModel class only
So How can I reach to "FirstButtonEvent" and "SecondButtonEvent" of selected user control of list view
The way with commands and MVVM is preferable, but you can also work with custom RoutedEvent instead of Event:
public sealed partial class TestingUerControl : UserControl
{
public TestingUerControl()
{
this.InitializeComponent();
}
public static readonly RoutedEvent FirstButtonEvent = EventManager.RegisterRoutedEvent(
nameof(FirstButton), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TestingUerControl));
public event RoutedEventHandler FirstButton
{
add { AddHandler(FirstButtonEvent, value); }
remove { RemoveHandler(FirstButtonEvent, value); }
}
private void firstButton_Click(object sender, RoutedEventArgs e)
{
//Some stuff of code
RaiseEvent(new RoutedEventArgs(TestingUerControl.FirstButtonEvent));
}
private void secondButton_Click(object sender, RoutedEventArgs e)
{
//See first btn
}
}
and then in XAML just assign an event handler:
<ListView x:Name="listUserControl" Width="100" Header="400">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:MyModel">
<userControl:TestingUerControl FirstButton="OnFirstButton_Click"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Use MVVM:
public class TestCommand : ICommand
{
Action<object> _execute;
Func<object, bool> _canExecute;
public TestCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public TestCommand(Action<object> execute, Func<object, bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
this._execute = execute;
this._canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
if (_canExecute != null)
{
return _canExecute(parameter);
}
else
{
return false;
}
}
public void Execute(object parameter)
{
_execute(parameter);
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
}
public class MyModel
{
public MyModel()
{
FirstButtonCmd = new TestCommand(OnFirstButtonCmd);
SecondButtonCmd = new TestCommand(OnSecondButtonCmd);
}
public ICommand FirstButtonCmd{get;set;}
public ICommand SecondButtonCmd{get;set;}
private void OnFirstButtonCmd()
{
//click first button
}
private void OnSecondButtonCmd()
{
//click second button
}
}
TestingUerControl.xaml
<Button Click={Binding FirstButtonCmd}></Button>
<Button Click={Binding SecondButtonCmd}></Button>
TestingUerControl.xaml.cs
public sealed partial class TestingUerControl : UserControl
{
public TestingUerControl()
{
this.InitializeComponent();
}
}
I have simplified example:
XAML:
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Name="cb" />
<Button Name="button1" Click="button1_Click" />
Code behind:
public partial class MainWindow : Window
{
private ObservableCollection<MyObject> collection = new ObservableCollection<MyObject>();
public MainWindow()
{
InitializeComponent();
collection.Add(new MyObject(true));
//grid.DataContext = collection[0];
}
private void button1_Click(object sender, RoutedEventArgs e)
{
collection[0].IsSelected = false;
}
}
public class MyObject
{
public bool IsSelected { get; set; }
public MyObject(bool isSelected)
{
this.IsSelected = isSelected;
}
}
The cb.IsChecked doesn't change by button clicking though the collection[0].IsSelected is changed.
Even if I uncomment grid.DataContext = collection[0]; - nothing changed.
In real example I have the same checkbox in the item template of a listbox. So the behaviour is the same - the selection of checkboxes don't change.
You need to implement INotifyPropertyChanged on your MyObject type
Please try the following codes:
public class MyObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
public MyObject(bool isSelected)
{
this.IsSelected = isSelected;
}
}
Does anyone know how to make a custom ItemsSource?
What I want to do is to make an itemsSource to my own UserControl so that it could be bound by ObservableCollection<>.
Also, I could know Whenever the number of items in the itemsSource updated, so as to do further procedures.
Thank you so much.
You may need to do something like this in your control
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
// Remove handler for oldValue.CollectionChanged
var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged;
if (null != oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
// Add handler for newValue.CollectionChanged (if possible)
var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (null != newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
}
void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Do your stuff here.
}
Use a DependencyProperty ItemsSource in your CustomControl and then bind to this DependencyProperty
This is the XAML-Code (Recognize the DataContext of the ListBox):
<UserControl
x:Name="MyControl">
<ListBox
DataContext="{Binding ElementName=MyControl}"
ItemsSource="{Binding ItemsSource}">
</ListBox>
</UserControl>
This is the CodeBehind:
public partial class MyCustomControl
{
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable),
typeof(ToolboxElementView), new PropertyMetadata(null));
}
This is the Code, where you use your "MyCustomControl":
<Window>
<local:MyCustomControl
ItemsSource="{Binding MyItemsIWantToBind}">
</local:MyCustomControl>
</Window>
Simplified answer.
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(null, (s, e) =>
{
if (s is UserControl1 uc)
{
if (e.OldValue is INotifyCollectionChanged oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= uc.ItemsSource_CollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += uc.ItemsSource_CollectionChanged;
}
}
}));
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Logic Here
}
// Do Not Forget To Remove Event On UserControl Unloaded
private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
{
if (ItemsSource is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= ItemsSource_CollectionChanged;
}
}