I am using an MVVM pattern and have a ComboBox that binds to properties in the viewmodel like this:
<ComboBox ItemsSource="{Binding Path=ItemCollection}"
SelectedItem="{Binding Path=SelectedItem}">
</ComboBox>
This works fine. In the viewModel I have
private MyData _selectedItem;
public List<MyData> ItemCollection { get; set; }
public MyData SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChanged();
}
}
which also works fine. The ItemCollection binds to the ItemSource of the ComboBox and the SelectedItem gets updated when a new item is selected in the ComboBox.
I want to manually change the SelectedItem in specific cases. Like this (I skip null checks for the sake of simplicity):
public MyData SelectedItem
{
get { return _selectedItem; }
set
{
if (value.Name == "Jump to the First item")
_selectedItem = ItemCollection.First();
else
_selectedItem = value;
RaisePropertyChanged();
}
}
This assumes that the type MyData has a string property thats called Name.
The problem is that if the conditional statement is true, the ItemSource of the ComboBox WILL get updated, however the actual visible selection of the the comboBox will not.
To give some context the comboBox actually binds to a CompositeCollection where there is one item that is styled as a button, so when clicked a dialog box is opened and the result of the dialogresult is determining what item in the comboBox should be selected..
-- Just no matter what I do the "Button" will always stay selected.
Is your INotifyPropertyChanged interface implemented properly?
Usually you would put your property name e.g. SelectedItem in the call to the function you raise the PropertyChanged event with.
Example courtesy of MSDN below.
https://msdn.microsoft.com/en-us/library/ms743695(v=vs.110).aspx
namespace SDKSample
{
// This class implements INotifyPropertyChanged
// to support one-way and two-way bindings
// (such that the UI element updates when the source
// has been changed dynamically)
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public Person()
{
}
public Person(string value)
{
this.name = value;
}
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("PersonName");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
}
The problem was that the comboBox got confused when trying to set a selected item while a it was already in the process of setting another selected item.
Solution is to set the IsAsync property to true so the SelectedItem will be set asynchronously.
<ComboBox ItemsSource="{Binding Path=ItemCollection}"
SelectedItem="{Binding Path=SelectedItem, IsAsync=True}">
</ComboBox>
When doing this it is important to invoke code back on the mainthread:
Application.Current.Dispatcher.Invoke(() =>
{
/* code here */
}
});
Related
I have a combobox and its datacontext property is assigned to ObservableCollection.
<ComboBox
Name="CB"
ItemsSource="{Binding}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True">
</ComboBox>
<TextBlock
Name="TB"
Text="{Binding ElementName=CB,Path=SelectedItem.Name,UpdateSourceTrigger=PropertyChanged}"
></TextBlock>
C#
class Person
{
public string Name { get; set; }
}
ObservableCollection<Person> people = new ObservableCollection<Person>( new List<Person>()
{
new Person(){Name="A"},
new Person(){Name="B"},
new Person(){Name="C"},
new Person(){Name="D"},
});
people[0].Name = "Z"; // When button clicked, i execute this
I also have a button when clicked updates the first employee's Name to something else.
When the first employee is the selected one, and I clicked the button even though underlying name changes, it's not reflected in the UI until I change the selected item and re-select the first one.
What should I do to achieve what I want? I thought when ObservableCollection that combobox is bound to changes, it would have been reflected in the ui.
The underlying object (i.e. Person) of the ObservableCollection needs to implement INotifyPropertyChanged interface.
class Person : INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
if(value != name)
{
name = value;
RaisePropertyChange("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChange(string propertyname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
}
}
I'm slightly confused about how to set up a CheckBox with a binding that ensures that my ViewModel is populated with all the checked fields. I have provided some of the code and a description at the bottom.
My Xaml file let's call it TreeView.xaml:
<TreeView x:Name="availableColumnsTreeView"
ItemsSource="{Binding Path=TreeFieldData, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate x:Uid="HierarchicalDataTemplate_1" ItemsSource="{Binding Path=Children, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected, Mode=TwoWay}">
<TextBlock x:Uid="TextBlock_1" Text="{Binding DisplayName.Text, Mode=OneWay}" />
</CheckBox>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The "code behind" TreeView.xaml.cs
public partial class MultipleColumnsSelectorView : UserControl
{
public MultipleColumnsSelectorView()
{
InitializeComponent();
}
private MultipleColumnsSelectorVM Model
{
get { return DataContext as MultipleColumnsSelectorVM; }
}
}
The ViewModel (tried to include only the relevant stuff) MultipleColumnsSelectorVM:
public partial class MultipleColumnsSelectorVM : ViewModel, IMultipleColumnsSelectorVM
{
public ReadOnlyCollection<TreeFieldData> TreeFieldData
{
get { return GetValue(Properties.TreeFieldData); }
set { SetValue(Properties.TreeFieldData, value); }
}
public List<TreeFieldData> SelectedFields
{
get { return GetValue(Properties.SelectedFields); }
set { SetValue(Properties.SelectedFields, value); }
}
private void AddFields()
{
//Logic which loops over SelectedFields and when done calls a delegate which passes
//the result to another class. This works, implementation hidden
}
The model TreeFieldData:
public class TreeFieldData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<TreeFieldData> Children { get; private set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
The Problem:
The behaviour that I want is when the user checks a checkbox, it should set the IsSelected property of TreeField (it does that right now) but then I want to go back to the ViewModel and make sure that this specific TreeField is added to SelectedFields. I don't really understand what the PropertyChangedEvent.Invoke does and who will receive that event? How can I make sure that SelectedFields gets populated so when AddFields() is invoked it has all the TreeField data instances which were checked?
You could iterate through the TreeFieldData objects in the TreeFieldData collection and hook up an event handler to their PropertyChanged event and then add/remove the selected/unselected items from the SelectedFields collection, e.g.:
public MultipleColumnsSelectorVM()
{
Initialize();
//do this after you have populated the TreeFieldData collection
foreach (TreeFieldData data in TreeFieldData)
{
data.PropertyChanged += OnPropertyChanged;
}
}
private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
TreeFieldData data = sender as TreeFieldData;
if (data.IsSelected && !SelectedFields.Contains(data))
SelectedFields.Add(data);
else if (!data.IsSelected && SelectedFields.Contains(data))
SelectedFields.Remove(data);
}
}
The subscriber of the PropertyChanged event is the view, so that if you change IsSelected programmatically the view knows it needs to update.
To insert the selected TreeField into your list you would add this code to your setter.
Also, you could define the following function which makes the notification much easier if you have many properties:
private void NotifyPropertyChange([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
The CallerMemberName attribute instructs the compiler to automatically insert the name of the property calling the method. The ? after PropertyChanged is a shorthand to your comparison to not null.
The setter of IsSelected can then be changed to
set
{
_isSelected = value;
if (value) { viewModel.SelectedFields.Add(this); }
else { viewModel.SelectedFields.Remove(this); }
NotifyPropertyChange();
}
Of course you would need to provide the TreeFieldData with the ViewModel instance, e.g. in the constructor.
I don't know if SelectedFields is bounded/shown in your view. If yes and you want the changes made to the list to be shown, you should change List to ObservableCollection.
I am very new to the concept of data binding and I don't think I understood it completely. I have a class named Project with a LinkedList of type ToDo as one of its properties. When I navigate to one instance of Project, I will display the LinkedList of type ToDo in a ListView. I have created functions that allow me to change the sequences of the nodes in the LinkedList (move up, move down) and to remove the selected node (delete). I want the ListView to refresh whenever there is a change in the LinkedList, (move up, move down or delete). However, I cannot achieve that. Here is my code: (not all parts are included)
XAML of the page:
<ListView x:Name="myListView" ItemsSource="{Binding Source={StaticResource ToDos}, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox x:Name="myCheckBox"
Content="{Binding ToDoTitle, Mode=TwoWay}"
IsChecked="{Binding IsCompleted, Mode=TwoWay}">
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C# for DataModel:
public class ToDo : INotifyPropertyChanged
{
private string toDoTitle;
private bool isCompleted;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string ToDoTitle { get { return this.toDoTitle; } set { this.toDoTitle = value; this.OnPropertyChanged(); } }
public bool IsCompleted { get { return this.isCompleted; } set { this.isCompleted = value; this.OnPropertyChanged(); } }
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Projects : INotifyPropertyChanged
{
private LinkedList<ToDo> toDos;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public LinkedList<ToDo> ToDos { get { return this.toDos; } set { this.toDos = value; this.OnCollectionChanged(); } }
public Projects()
{
ToDos = new LinkedList<ToDo>();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Thank you.
First I would advise you to read about MVVM, and try to follow some basic tutorials like this one.
You can use MVVM Light to avoid managing the INotifyPropertyChanged by yourself at first (but it's really good to know how MVVM light work under the hood).
To come back to your problem, your current code notifies only if you set the full ToDos list. If you want to be aware of any change in a list (seing when an item is add/remove/update), you are probably looking for an ObservableCollection, not a LinkedList.
Hope it helps.
I have a WPF ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
private string _sql;
public string Sql
{
get { return _sql; }
set
{
if (value == _sql) return;
OnPropertyChanged("Sql");
_sql = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I also have a XAML view with a TextBox
<Window.Resources>
<HbmSchemaExporter:MainWindowViewModel x:Key="viewModel"/>
</Window.Resources>
....
<TextBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Source={StaticResource ResourceKey=viewModel}, Path=Sql,Mode=OneWay}"/>
Code behind
private MainWindowViewModel ViewModel
{
get { return Resources["viewModel"] as MainWindowViewModel; }
}
The problem is that when in the code I do viewModel.Sql = SOMETHING the text box doesn't get updated. Debugger displays the correct value in the property but the textbox remains blank.
I also tried to change the binding to TwoWay but that only allows me to overwrite the property with a value I type in the textbox, which is something I don't really want (actually I still need to make it readonly, but it's currently out of scope).
How can I update the textbox after programmatically updating the property?
The application is basically a NHibernate DDL generator I'm writing after reading this. I need to press a "Generate SQL" button and it displays the code to run onto DB.
public string Sql
{
get { return _sql; }
set
{
if (value == _sql) return;
OnPropertyChanged("Sql");
_sql = value;
}
}
That does not make sense. At the point that any PropertyChanged event handler is called, reading Sql will still give the old value, because you haven't updated _sql yet. You need to first update the value, and only then raise the PropertyChanged event.
A listbox works as an auto-complete within a richtextbox I am populating it with items from a collection. I need it to auto select first item every time listbox populates.
How do I do this?
Thank you
foreach (var ks in ksd.FindValues(comparable))
{
lb.Items.Add(ks.Value);
}
if (lb.HasItems)
{
lb.Visibility = System.Windows.Visibility.Visible;
lb.SelectedIndex = 0; //Suggested solution, still doesn't work
}
else
{
lb.Visibility = System.Windows.Visibility.Collapsed;
}
You can put SelectedIndex to 0 in XAML for the first time loading
<ListBox SelectedIndex="0" />
In code-behind, you can do this after loading items list
if (this.lst.Items.Count > 0)
this.lst.SelectedIndex = 0;
If you're using MVVM then you can also try another solution:
Add property called SelectedValue to the ViewModel;
After loading (or adding) values to the List that you bind to the ListBox set SelectedValue withvaluesList.FirstOrDefault();
On the XAML bind the SelectedItem property of the ListBox to SelectedValue
(from ViewModel) and set binding Mode="TwoWay"
This should work:
listBox1.SetSelected(0,true);
You don't need anything just the data you use. You shouldn't be interested how the Control looks like.
(You don't want to be coupled with that control)
<ListBox ItemsSource="{Binding MyItems}" SelectedItem="{Binding MyItem}" />
could be:
<SexyWoman Legs="{Binding MyItems}" Ass="{Binding MyItem}" />
and it will work as well.
The ListBox has this class as a DataContext:
class DummyClass : INotifyPropertyChanged
{
private MyItem _myItem;
public MyItem MyItem
{
get { return _myItem; }
set { _myItem = value; NotifyPropertyChanged("MyItem"); }
}
private IEnumerable<MyItem> _myItems;
public IEnumerable<MyItem> MyItems
{
get { return _myItems; }
}
public void FillWithItems()
{
/* Some logic */
_myItems = ...
NotifyPropertyChanged("MyItems");
/* This automatically selects the first element */
MyItem = _myItems.FirstOrDefault();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string value)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(value));
}
}
#endregion
}