How to update bindings inside DataTemplate? - wpf

Let's say I have the following ListView in my view
<ListView x:Name="ListView1" ItemsSource="{Binding SomeCollection}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Do something" Command="{Binding SomeCommand, Mode=OneWay}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate DataType="model:Person">
<StackLayout>
<TextBlock Text="{Binding Name}">
<Image Source="{Binding State, Converter={StaticResource StateToIconConverter}, Mode=OneWay}" />
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now, this Person is a model and its properties do not have any way to inform the view of them being updated (according to MVVC). But, I need to update the view after SomeCommand gets executed, because items inside SomeCollection get edited.
I tried doing this
public void ExecuteSomeCommand() {
// change the state of some person inside SomeCollection
(ListView1.SelectedItems[0] as Person).State = "Some different state";
// now inform the view of change, so it can reflect in the DataTemplate
ListView1.GetBindingExpression(ListBox.ItemsSourceProperty).UpdateTarget();
}
And I thought this would propagate into the DataTemplate, but it doesn't. Is there any other way to do this? How should I change my approach?

When the model used in data binding implements the INotifyPropertyChanged Interface the UI is automatically updated when you modify a property of the model.
public class Person : INotifyPropertyChanged
{
private Image _state;
public Image State
{
get => _state;
set {
if (value != _state) {
_state = value;
OnPropertyChanged(nameof(State));
}
}
}
// ... other properties here ...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

Display only one TextBox which is bound to selected item from ListView

I have a ListView which is bound to an ObservableCollection
I have added a DataTemplate to a ListView to bind item with TextBox in purpose to Rename selectedItem using Rename from ContextMenu:
View
<ListView DockPanel.Dock="Left"
Background="MidnightBlue"
Width="140"
SelectedItem="{Binding SelectedNotebook, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Notebooks}"
x:Name="notebooksList"
SelectionChanged="notebooksList_SelectionChanged"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<uc:DisplayNotebook Notebook="{Binding}">
<uc:DisplayNotebook.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename"
Command="{Binding Source={StaticResource vm}, Path=EditCommand}"
CommandParameter="{Binding SelectedNotebook}"/>
<MenuItem Header="Delete"
Command="{Binding Source={StaticResource vm}, Path=DeleteNotebookCommand}"
CommandParameter="{Binding SelectedNotebook}"/>
</ContextMenu>
</uc:DisplayNotebook.ContextMenu>
</uc:DisplayNotebook>
<TextBox Text="{Binding Name, Mode=TwoWay}"
Visibility="{Binding Source={StaticResource vm}, Path=IsVisible}"
x:Name="notebookTextBox">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding Source={StaticResource vm}, Path=EndEditingCommand}"
CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public async void StopEditingNotebook(Notebook notebook)
{
IsVisible = Visibility.Collapsed;
await DatabaseHelper.Update(notebook);
GetNotebooks();
}
public async void StopEditingNote (Note note)
{
IsVisible = Visibility.Collapsed;
await DatabaseHelper.Update(note);
GetNotes();
}
Commands
public class EndEditingCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public NotesVM ViewModel { get; set; }
public EndEditingCommand(NotesVM vm)
{
ViewModel = vm;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Notebook notebook = parameter as Notebook;
if (notebook != null)
ViewModel.StopEditingNotebook(notebook);
}
}
public class EditCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public NotesVM ViewModel { get; set; }
public EditCommand(NotesVM vm)
{
ViewModel = vm;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ViewModel.StartEditing();
}
}
I'd like to Rename only one item at once by popping up one TextBox instead of all TextBoxes in the same time(this what is happening currently due to bound TextBox in DataTemplate).
I was wondering about finding an ID of selectedItem and then somehow display only this particular TextBox.
Do you have any ideas on this matter?
Thanks for help in advance
You can use a MultiDataTrigger to toggle the Visibility of the TextBox. The conditions are IsSelected and IsVisible from your view model.
Alternatively (recommended), spend your item model an IsEditEnabled property (or move and rename the IsVisible property to the item model).
Currently, every item binds to the same IsVisible property of the same ViewModel instance.
Instead, each item must bind to its own property (that's why the property should be defined in the item model).
Note, a member named with the prefix "is" is expected to return a boolean.
In your case it would be semantically correct to name the property Visibility and not IsVisible.
Additionally, the CommandParameter binding inside the ContextMenu looks wrong too. It must be
<ContextMenu>
<MenuItem CommandParameter="{Binding}"/>
</ContextMenu>

WPF Bindings of a Custom User Control with a ComboBox containing a DataTemplate

I am trying to create a Custom User Control with a ListView in it that contains a Data Template looking like this:
<ComboBox ItemsSource="{Binding ItemsSource, ElementName=root}"
SelectedItem="{Binding SelectedItem, ElementName=root, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- This is what I tried:
Working but not what I want <TextBox Text="{Binding Name}"/>
Returns the List only the word "FallBack" <TextBox Text="{Binding ItemText, ElementName=root}" />
Returns the LIst empty <TextBox Text="{Binding ItemText}" />
-->
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In the code behind I have created the necessary dependency properties for this case (so I assume) the only relevant one is regarding the Item Text and it looks like that:
#region ItemText
public string ItemText
{
get { return (string)GetValue(ItemTextProperty); }
set { SetValue(ItemTextProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemTextProperty =
DependencyProperty.Register("ItemText", typeof(string), typeof(CardComboBox), new PropertyMetadata("FallBack"));
#endregion
What I would like to do is to add the User Control like that
<local:CardComboBox ItemsSource="{Binding Persons}" SelectedItem="{Binding SelectedPerson}" ItemText="{Binding Name}" IsEnabled="True" />
Option one:
<TextBox Text="{Binding Name}"/>
works fine because the PropertyName of the Class Person is Name, but I obviously do not want to HardCode it. I want to bind it to whichever property I like.
Option 2:
<TextBox Text="{Binding ItemText, ElementName=root}" />
Gives me the list of 7 items (as per list) but just displays the word "Fallback" because of the DependencyPropertyMetadata.
Option 3:
<TextBox Text="{Binding ItemText}" />
Gives me the list but with no text at all.
I also tried to work with relativeSource but only with similar results.
#region Person
Person _selectedPerson;
public Person SelectedPerson
{
get => _selectedPerson;
set
{
if (value != _selectedPerson)
{
_selectedPerson = value;
OnPropertyChanged("SelectedPerson");
}
}
}
ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons
{
get => _persons;
set
{
if (value != _persons)
{
_persons = value;
OnPropertyChanged("Persons");
}
}
}
public void populatePersons()
{
Persons = new ObservableCollection<Person>();
Persons.Add(new Person("Carl"));
Persons.Add(new Person("Max"));
Persons.Add(new Person("May"));
Persons.Add(new Person("Jen"));
Persons.Add(new Person("Charly"));
Persons.Add(new Person("Nora"));
Persons.Add(new Person("Yvonne"));
}
#endregion
I have added the List I am binding to. The Method Populate Persons is called in the constructor of the ViewModel.
<TextBox Text="{Binding ItemText, RelativeSource={RelativeSource AncestorType=local:CardComboBox}}" /> will bind the TextBox to the current value of the ItemText dependency property of the parent CardComboBox if that's what you want.
However, if you want to display the value of the Name property of each Person byt you also want to be able specify the name of the property of Person to bind to using your ItemText property, you will have to create the binding programmtically.
The closest solution working so far is to expose the ItemContentTemplate Dependency Property and then to bind it to a static resource (e.g. from App.xaml)
The XAML of the UserControl looks like this:
<StackPanel Style="{StaticResource CardStackPanel}" Orientation="{Binding Orientation, ElementName=root}" >
<Label x:Name="Label" Content="{Binding TitleText, ElementName=root}"/>
<ComboBox ItemsSource ="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
ItemTemplate="{Binding ItemContentTemplate, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay}" />
</StackPanel>
the Code Behind for the ComboBox Part:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CardComboBox));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(CardComboBox));
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public DataTemplate ItemContentTemplate
{
get { return (DataTemplate)GetValue(ItemContentTemplateProperty); }
set { SetValue(ItemContentTemplateProperty, value); }
}
public static readonly DependencyProperty ItemContentTemplateProperty =
DependencyProperty.Register("ItemContentTemplate", typeof(DataTemplate), typeof(CardComboBox));
The Static Ressource in App.xaml (example):
<DataTemplate x:Key="FirstNamesTemplate">
<Label Content="{Binding FirstName}"/>
</DataTemplate>
The Implementation of a ComboBoxCard looks now like this:
<local:CardComboBox ItemsSource="{Binding PersonModels}" SelectedItem="{Binding SelectedPersonModel, Mode=TwoWay}" ItemContentTemplate="{StaticResource FirstNamesTemplate}" TitleText="With StaticResource" IsEnabled="False"/>
This pattern allows to implement ComboBoxes or ListViews in Custom UserControls .

Checkbox not showing as checked inside ListView

I have a checkbox inside a listview. The listview is bound to an observable collection. When I use the context menu to try to select all Checkboxes, they do not show as checked. What am I doing wrong?
<ListView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding AvailableModels}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
SelectionMode="Extended">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Select All Models" Command="{Binding
SelectAllModelsAction}" />
<MenuItem Header="Deselect All Models" Command="{Binding
DeselectAllModelsAction}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding DataContext.IsSelected,
RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type
ListViewItem}}}" VerticalAlignment="Center" />
<Label Content="{Binding Name}" Margin="2,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public ObservableCollection<ListItems> AvailableModels
{
get
{
return this.availableModels;
}
set
{
this.availableModels = value;
this.NotifyPropertyChanged(m => m.AvailableModels);
}
}
Context Menu Action
private void SelectAllModels()
{
foreach (var model in this.AvailableModels)
{
model.IsSelected = true;
}
this.NotifyPropertyChanged(m => m.AvailableModels);
}
ListItems Object
public class ListItems
{
public string Name
{
get;
set;
}
public object Value
{
get;
set;
}
public bool IsSelected
{
get;
set;
}
}
ListItems isn't implementing INotifyPropertyChanged so changing IsSelected isn't updating the UI.
Calling NotifyPropertyChanged in SelectAllModels() is unnecessary since the collection itself isn't changed. The NotifyProperyChanged() call in the AvailableModels setter updates the UI when a new collection is set. And ObservableCollection will handle when the collection is modified (items added/removed). However changes to the properties within the ListItems does not update the UI unless they call NotifyPropertyChanged in the setters.

WPF RadioButtons in details UserControl unchecked when navigating through parent ListView

---edited to include full code sample---
I know there have been a lot of issues with the .NET 3.5 RadioButton, but I believe this is a different issue that I have not found anything on in my searching. I've put together a very minimal code sample to demonstrate the problem I'm observing.
I have a ListView in my XAML that has its SelectedItem bound to the SelectedModel in my ViewModel. This same XAML contains a StackPanel that shows RadioButton options for the SelectedModel when navigating through items in the ListView. Everything works great until I add a RadioButton group into the mix. Now, if I select a record in the ListView that has one of the RadioButton options checked, then select another record in the ListView that has a different option checked, and now select the previous record in the ListView again, all of the RadioButtons for that ListView item will be unchecked. If I move up and down through the ListView long enough, every RadioButton, and the underlying Boolean values, will be set to false.
Model code:
public class Model
{
public string Name { get; set; }
public bool IsOne { get; set; }
public bool IsTwo { get; set; }
public bool IsThree { get; set; }
}
ViewModel code:
public class ViewModel: INotifyPropertyChanged
{
private Model _selectedModel;
public event PropertyChangedEventHandler PropertyChanged;
public List<Model> ModelList { get; set; }
public Model SelectedModel
{
get { return _selectedModel; }
set
{
_selectedModel = value;
OnPropertyChanged("SelectedModel");
}
}
public ViewModel()
{
ModelList = new List<Model>();
ModelList.Add(new Model() {Name = "Florida"});
ModelList.Add(new Model() {Name = "Texas"});
ModelList.Add(new Model() {Name = "Arizona"});
ModelList.Add(new Model() {Name = "Washington"});
}
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
And finally, the XAML code:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Path=ModelList}"
SelectedItem = "{Binding SelectedModel}"
DisplayMemberPath="Model">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding Name}"
Width="100"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Column="1">
<RadioButton x:Name="IsOne" Content="One" Margin="3"
IsChecked="{Binding Path=SelectedModel.IsOne}"
GroupName="{Binding Path=SelectedModel.Name}"/>
<RadioButton x:Name="IsTwo" Content="Two" Margin="3"
IsChecked="{Binding Path=SelectedModel.IsTwo}"
GroupName="{Binding Path=SelectedModel.Name}" />
<RadioButton x:Name="IsThree" Content="Three" Margin="3"
IsChecked="{Binding Path=SelectedModel.IsThree}"
GroupName="{Binding Path=SelectedModel.Name}" />
</StackPanel>
</Grid>
Any ideas of what would be causing the binding to behave in the manner?
Thanks,
Glenn
If you have a number of RadioButtons in each row of your ListView, you will need to provide a unique GroupName for each set of RadioButtons in each row. The only way that I know how to do that is if you add an extra property to the data type class that you are displaying in the GridView. It doesn't matter what that value is as long as each item in the collection has a different value.
You can then Bind the new item property to the GroupName property of each RadioButton in the set:
<StackPanel>
<RadioButton Content="Demo" GroupName="{Binding GroupName}" ... />
<RadioButton Content="Screener" GroupName="{Binding GroupName}" ... />
<RadioButton Content="Vote" GroupName="{Binding GroupName}" ... />
<RadioButton Content="Vote Follow Up" GroupName="{Binding GroupName}" ... />
<RadioButton Content="Named Vote" GroupName="{Binding GroupName}" ... />
</StackPanel>
I found an article that demonstrates this nicely with XAML examples:
Grouped RadioButton for WPF Datagrid
UPDATE >>>
Yep, in your situation, when you have a group of RadioButtons in a container panel of some kind, you don't even need to supply a GroupName as the Framework does that for us 'under the covers'. I refactored your XAML to use a different Binding to see if that made any difference, but it didn't. You've found a bone fide bug there. Here is the XAML that I ended with that still exhibits the same problem:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding ModelList}" IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding SelectedModel}" DisplayMemberPath="Model">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Column="1">
<RadioButton x:Name="IsOne" Content="One" Margin="3" IsChecked="{Binding ModelList/IsOne}" />
<RadioButton x:Name="IsTwo" Content="Two" Margin="3" IsChecked="{Binding ModelList/IsTwo}" />
<RadioButton x:Name="IsThree" Content="Three" Margin="3" IsChecked="{Binding ModelList/IsThree}" />
</StackPanel>
</Grid>
I removed the unused properties from your code example in your question. I would also suggest that you edit your question with a more accurate description of the problem... although I didn't quite work out the pattern of clicks that it would take to uncheck a CheckBox, I could see that it has nothing to do with the third RadioButton in particular. It certainly happened on the other two as well.
It might be worth reporting this as a bug on the Microsoft Connect website... perhaps there's an explanation or workaround there already?
I have confirmed that this is a known issue that has continued into Framework 4.5. Franklin Chen provided me with a nice little workaround that extends the RadioButton class.
Here is a link to his answer.
Here is his code. Now just bind to the IsCheckedReal property and everything works.
public class RadioButtonExtended : RadioButton
{
static bool m_bIsChanging = false;
public RadioButtonExtended()
{
this.Checked += new RoutedEventHandler(RadioButtonExtended_Checked);
this.Unchecked += new RoutedEventHandler(RadioButtonExtended_Unchecked);
}
void RadioButtonExtended_Unchecked(object sender, RoutedEventArgs e)
{
if (!m_bIsChanging)
this.IsCheckedReal = false;
}
void RadioButtonExtended_Checked(object sender, RoutedEventArgs e)
{
if (!m_bIsChanging)
this.IsCheckedReal = true;
}
public bool? IsCheckedReal
{
get { return (bool?)GetValue(IsCheckedRealProperty); }
set
{
SetValue(IsCheckedRealProperty, value);
}
}
// Using a DependencyProperty as the backing store for IsCheckedReal. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckedRealProperty =
DependencyProperty.Register("IsCheckedReal", typeof(bool?), typeof(RadioButtonExtended),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
IsCheckedRealChanged));
public static void IsCheckedRealChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
m_bIsChanging = true;
((RadioButtonExtended)d).IsChecked = (bool)e.NewValue;
m_bIsChanging = false;
}
}

WPF ComboBox Display Issue

I have a ComboBox that's bound to a Collection of User objects. The combo's DisplayMemberPath is set to "Name," a property of the User object. I also have a textbox that is bound to the same object that ComboBox.SelectedItem is bound to. As such, when I change the text in the TextBox, my change gets immediately reflected in the combo. This is exactly what I want to happen as long as the Name property isn't set to blank. In such a case, I'd like to substitute a generic piece of text, such as "{Please Supply a Name}". Unfortunately, I couldn't figure out how to do so, so any help in this regard would be greatly appreciated!
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="340"
SizeToContent="Height"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize">
<StackPanel>
<TextBlock Text="ComboBox:" />
<ComboBox SelectedItem="{Binding SelectedUser}"
DisplayMemberPath="Name"
ItemsSource="{Binding Users}" />
<TextBlock Text="TextBox:"
Margin="0,8,0,0" />
<TextBox Text="{Binding SelectedUser.Name, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
public class ViewModel : INotifyPropertyChanged
{
private List<User> users;
private User selectedUser;
public event PropertyChangedEventHandler PropertyChanged;
public List<User> Users
{
get
{
return users;
}
set
{
if (users == value)
return;
users = value;
RaisePropertyChanged("Users");
}
}
public User SelectedUser
{
get
{
return selectedUser;
}
set
{
if (selectedUser == value)
return;
selectedUser = value;
RaisePropertyChanged("SelectedUser");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class User
{
public string Name { get; set; }
}
Take a look at this post. There are several answers that may meet your requirement.
You can make use of TargetNullValue
<StackPanel>
<TextBlock Text="ComboBox:" />
<ComboBox SelectedItem="{Binding SelectedUser}" ItemsSource="{Binding Users}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name, TargetNullValue='Enter some text'}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="TextBox:"
Margin="0,8,0,0" />
<TextBox Text=
"{Binding SelectedUser.Name, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
and convert an empty name to null.
public class User
{
private string name;
public string Name
{
get
{
return this.name;
}
set
{
this.name = (string.IsNullOrEmpty(value)) ? null : value;
// probably best raise property changed here
}
}
}

Resources