I have a Combobox that bound a list of Contact defined in that way:
public List<Contact> Contacts { get;set; } = new List<Contact>();
public class Contact
{
public bool IsFavourite { get; set; }
public bool IsChecked { get; set; }
public string Name { get; set; }
}
NB: the Contact class implement INotifyPropertyChange, that I don't wrote in the example.
The Contacts list is bounded on the ComboBox in the following way:
<ComboBox ItemsSource="{Binding Contacts}"
ItemTemplate="{StaticResource CombinedTemplate}" />
where CombinedTemplate contains the following:
<DataTemplate x:Key="NormalItemTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Name}" Checked="Contact_Checked" Unchecked="Contact_Unchecked" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter" Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}" />
</DataTemplate>
as you can see I bounded the IsChecked of CheckBox to the IsChecked property that is valorized behind code, so suppose that in the Contacts list is added this item:
var contact = new Contact();
contact.IsChecked = true;
contact.IsFavourite = true;
contact.Name = "Foo";
Contacts.Add(contact);
the Checked of the CheckBox should firing automatically 'cause the IsChecked is true, but I doesn't get this working.
What I did wrong? Thanks.
UPDATE
As suggested in the answer for fix this "bug" I should handle the Loaded event, so I did:
<ComboBox ItemsSource="{Binding Contacts}"
Loaded="ContactMenuComboBox_Loaded"
ItemTemplate="{StaticResource CombinedTemplate}" />
in the event I did:
private void ContactMenuComboBox_Loaded(object sender, RoutedEventArgs e)
{
foreach (var contact in Contacts)
{
if (contact.IsFavourite)
{
contact.IsChecked = true;
}
}
}
the IsChecked property is setted correctly, unfortunately the IsChecked event isn't firing.
Forget to say, if I put the code of ContactMenuComboBox_Loaded inside a button, and then press it, well the event IsChecked will firing.
This is a really weird situation.
UPDATE #2
This is the content of Checked event:
private void Contact_Checked(object sender, RoutedEventArgs e)
{
var contact = (sender as CheckBox).DataContext as CheckedListItem<Model.Contact>;
Task.Factory.StartNew(() =>
{
ContactController.GetLeagues(contact);
})
.ContinueWith((prevTask) =>
{
CheckTaskException(prevTask);
});
}
Update
I have checked with WPF and you are indeed right! The first binding evaluation does not fire the Checked event, only the subsequent calls do. This is in contrast to UWP, where the event is fired.
As a workaround, you could handle the Loaded event if you need to perform an action right after the value is first bound. However, if you are implementing the app as MVVM, you might be better off pushing the Checked logic in the setter of the IsChecked property.
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Name}"
Loaded="Contact_Loaded"
Checked="Contact_Checked" Unchecked="Contact_Unchecked" />
And in the Loaded method do something like:
var checkbox = (CheckBox)sender;
if ( checkbox.IsChecked ) Contact_Checked(this, e);
if ( !checkbox.IsChecked ) Contact_UnChecked(this, e);
Update 2
If you just want to execute the actions as soon as possible, you can indeed attach the Loaded event to your window and do the following:
foreach (var contact in Contacts)
{
if (contact.IsFavourite)
{
Task.Factory.StartNew(() =>
{
ContactController.GetLeagues(contact);
})
.ContinueWith((prevTask) =>
{
CheckTaskException(prevTask);
});
}
}
Of course to avoid code duplication, you could extract this code into a separate method with a Contact parameter.
Original answer
The problem is that your Contacts property is a normal List. In this case the data-bound controls will not update with any changes to that list after first binding. You should use ObservableCollection<T> instead:
public ObservableCollection<Contact> Contacts { get; set; } =
new ObservableCollection<Contact>();
In addition I don't see a point of having two data templates when one of them contains just a ContentPresenter for the other. You could simplify it into just NormalItemTemplate:
<ComboBox ItemsSource="{Binding Contacts}"
ItemTemplate="{StaticResource NormalItemTemplate}" />
Finally, the data-binding for Name is not correct, the Item. prefix should not be there:
<DataTemplate x:Key="NormalItemTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Name}"
Checked="Contact_Checked" Unchecked="Contact_Unchecked" />
</StackPanel>
</DataTemplate>
Related
I am learning MVVM pattern while refactoring an app to MVVM.
I have a model class Machine that provides a list of installations in a form of ObservableCollection<Installation> Installations.
In one of the windows (views) I need to display only those installations that have updates (thus meet the following criteria):
private void InstallationsToUpdateFilter(object sender, FilterEventArgs e)
{
var x = (Installation)e.Item;
bool hasNewVersion = ShowAllEnabledInstallations ? true : x.NewVersion != null;
bool isSetAndOn = !String.IsNullOrEmpty(x.Path) && x.CheckForUpdatesFlag;
e.Accepted = isSetAndOn && hasNewVersion;
}
private void OnFilterChanged()
{
installationsToUpdateSource?.View?.Refresh();
}
I am doing this by filtering in my ViewModel:
class NewVersionViewModel : ViewModelBase
{
private Machine machine = App.Machine;
...
public NewVersionViewModel(...)
{
...
InstallationsToUpdate.CollectionChanged += (s, e) =>
{
OnPropertyChanged("NewVersionsAvailableMessage");
OnFilterChanged();
};
installationsToUpdateSource = new CollectionViewSource();
installationsToUpdateSource.Source = InstallationsToUpdate;
installationsToUpdateSource.Filter += InstallationsToUpdateFilter;
}
public ObservableCollection<Installation> InstallationsToUpdate
{
get { return machine.Installations; }
set { machine.Installations = value; }
}
internal CollectionViewSource installationsToUpdateSource { get; set; }
public ICollectionView InstallationsToUpdateSourceCollection
{
get { return installationsToUpdateSource.View; }
}
...
}
This is done by custom ListView:
<ListView ItemsSource="{Binding InstallationsToUpdateSourceCollection}" ... >
...
<ListView.ItemTemplate>
<DataTemplate>
<Grid ...>
<Grid ...>
<CheckBox Style="{StaticResource LargeCheckBox}"
IsChecked="{Binding Path=MarkedForUpdate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding Path=HasNewVersion}"
/>
</Grid>
<Label Content="{Binding Path=InstalledVersion.Major}" Grid.Column="1" Grid.Row="0" FontSize="50" FontFamily="Segoe UI Black" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,-10,0,0"/>
...
<Grid.ContextMenu>
<ContextMenu>
...
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
All of this works - until I try to "send" <CheckBox IsChecked="{Binding Path=MarkedForUpdate... back to my model - so it will be stored there.
How it can be done? (Can I have some kind of setter on ICollectionView?)
Current architecture can be changed. What I ultimately need:
Display items (installations) from model in ListView (currently: works)
Filter/Show only installations that meet some criteria (currentrly: works)
Reflect changes in MarkedForUpdate checkbox back to model (currently: not working)
I've googled a lot but was unable to find a relevant solution or suggestions.
Any help would be greatly appreciated. Thanks!
I figured the problem out. Although it was a silly mistake, I still want to share it to save someone's time.
The model itself updates in the configuration described above. The problem was that what model property (Machine.Installations in my case) did not implement INotifyPropertyChanged interface so other Views (through their corresponding ViewModels) were not aware of changes. Thus one should use OnPropertyChanged/RaisePropertyChanged not only in ViewModel, but in Model as well.
Hope this may help someone.
I have the following VM class:
public class SubModuleVM:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string _name;
public string Name
{
get { return_name; }
set {
_name = value;
PropertyChanged?.(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
public class ProjectViewVM:INotifyPropertyChanged
{
public ReadOnlyObservableCollection<SubModuleVM> SubModules
{get;set;}
public ProjectViewVM()
{
SubModules=LoadFromDatabase(); //loads from database
}
// snip irrelevant details
}
And the XAML class that corresponds to ProjectViewVM:
<ListView ItemsSource="{Binding SubModules}" SelectedItem="{Binding CurrentModule}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Name}" Grid.Column="0" Margin="0,0,5,0"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Take note that the Collection is ReadOnlyObservableCollection, not sure whether that will cause problem to the binding or not
When the user changes the Name from the ListView in the UI, I want to validate so that all the SubModuleVM.Name inside the ProjectViewVM are all unique. In other words I don't allow duplication on SubModuleVM.Name inside a SubModules collection. If the user renames it to a value already existing, then I want the validation event ( at UI level) to fire and informs user that this is not permissible, and then the name shall revert back to the old one.
I think that the ListView should have such a validation event available for capturing, but so far I can't find any.
How can I implement my requirement?
Whenever the set on Name property is called during update or edit of name in list view as you mentioned, try calling logic similar to kind of below snippet :
private void Validate()
{
var query = SubModules.GroupBy(s => s.Name)
.Select(s => new { Key = s.Key, Count = s.Count() });
if(query.Any(a => a.Count > 1))
{
/* raise notification with some error message or use RasiePropertyChanged("InValid")
* where InValid property in VM is bing to your XAML for indicating error*/
}
}
this is my user template in listbox. i want to have "listbox.selectedindex" on clicking any checkbox of listbox. i want to knw of which row,checkbox is selected.like on click event of checkbox,it should focus the whole selected row.
<ListView x:Name="listbox3" Visibility="Visible" Margin="540,168,37,46" IsSynchronizedWithCurrentItem="True" BorderBrush="Black">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="200" >
<TextBlock Text="{Binding VmName}" Width="129" Visibility="Visible" />
<CheckBox x:Name="cb" IsThreeState="False" IsChecked="{Binding IsCheck, Mode=TwoWay}" Margin="6,0,18,6" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<CheckBox x:Name="cb1" IsThreeState="False" IsChecked="{Binding IsCheck1, Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Without a good, minimal, complete code example that clearly illustrates the problem, it is difficult to know exactly what advice would be most useful to you. Without a clear problem statement, it's not even completely clear what you want the code to do.
However, if I understand correctly, you are displaying some data item type, using a corresponding DataTemplate object in your ListView. The user may toggle the checkboxes, and you want to update the currently selected item in the ListView, so that it is always the item containing the checkbox that was just toggled.
There are least a couple of reasonable ways to do that. In both cases, you will simply set the ListView.SelectedValue property to data item object reference corresponding to the CheckBox that is being modified.
The first way involves handling the Checked and Unchecked events on the CheckBox controls themselves, tracking back to the ListViewItem and then obtaining the data item for that ListViewItem.
First, you will need to write a handler to do the above:
private void cb_Checked(object sender, RoutedEventArgs e)
{
ListViewItem listViewItem =
GetVisualAncestor<ListViewItem>((DependencyObject)sender);
listbox3.SelectedValue =
listbox3.ItemContainerGenerator.ItemFromContainer(listViewItem);
}
private static T GetVisualAncestor<T>(DependencyObject o) where T : DependencyObject
{
do
{
o = VisualTreeHelper.GetParent(o);
} while (o != null && !typeof(T).IsAssignableFrom(o.GetType()));
return (T)o;
}
Note the helper method GetVisualAncestor<T>(). It uses VisualTreeHelper to walk the tree back to the ListViewItem object that contains the CheckBox control that was affected.
With this object found, the code then calls ItemContainerGenerator.ItemFromContainer() to find the actual data item object reference, and assigns this reference to the SelectedValue property.
Of course, for the handler to be useful, you need to subscribe it to the relevant Checked and Unchecked events. For example:
<DataTemplate DataType="{x:Type local:DataItem}">
<StackPanel Orientation="Horizontal" Width="200" >
<TextBlock Text="{Binding VmName}" Width="129" Visibility="Visible" />
<CheckBox x:Name="cb" IsThreeState="False"
IsChecked="{Binding IsChecked1, Mode=TwoWay}"
Margin="6,0,18,6"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Checked="cb_Checked" Unchecked="cb_Checked"/>
<CheckBox x:Name="cb1" IsThreeState="False"
IsChecked="{Binding IsChecked2, Mode=TwoWay}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Checked="cb_Checked" Unchecked="cb_Checked"/>
</StackPanel>
</DataTemplate>
(Since you didn't provide a complete code example, in which the data item object class was included, I just wrote my own based on your question. I changed the property names so that they made more sense, i.e. IsChecked1 and IsChecked2. Feel free to use your own property names instead :) ).
The second way is a little more direct in one respect, but a little less direct in another respect. That is, assuming your data item object class implements INotifyPropertyChanged, you can subscribe to the PropertyChanged event for each data item object, and simply assign the sender of the event as the ListView.SelectedValue property.
This is more direct in that you don't have to add code that walks the visual tree back to some control's parent. But it's also less direct, in that you will need code that attaches the necessary event handler to each data item object.
An example of that might look like this:
List<DataItem> dataItems = new List<DataItem>
{
new DataItem { VmName = "sagar" },
new DataItem { VmName = "kaustubh" },
new DataItem { VmName = "gaurav" },
new DataItem { VmName = "abhi" },
};
listbox3.ItemsSource = dataItems;
PropertyChangedEventHandler handler =
(sender, e) => listbox3.SelectedValue = sender;
foreach (DataItem item in dataItems)
{
item.PropertyChanged += handler;
}
Note that in the above example, I assign the SelectedValue property on any property change. In my own code example, this is fine because the only properties that can change are the checkbox-related ones. And of course, this would also be fine if you want to select the corresponding ListView item on any property value change. But if you really only want to update on changes to the IsChecked1 and IsChecked2 properties, you'll need to look at the property name in the handler. E.g.:
PropertyChangedEventHandler handler = (sender, e) =>
{
if (e.PropertyName == "IsChecked1" || e.PropertyName == "IsChecked2")
{
listbox3.SelectedValue = sender;
}
}
Here is the DataItem class I wrote for the code examples for both approaches shown above:
class DataItem : INotifyPropertyChanged
{
private string _vmName;
private bool _isChecked1;
private bool _isChecked2;
public string VmName
{
get { return _vmName; }
set { _vmName = value; OnPropertyChanged(); }
}
public bool IsChecked1
{
get { return _isChecked1; }
set { _isChecked1 = value; OnPropertyChanged(); }
}
public bool IsChecked2
{
get { return _isChecked2; }
set { _isChecked2 = value; OnPropertyChanged(); }
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I have an application with several item controls (treeviews and others) which contain an item template with a checkbox inside. This checkbox checked state is bound to an IsChecked property of the item view model. This works correctly when clicking on the checkbox, but it's impossible to check/uncheck them with the keyboard (I believe this is due to the fact that the checkbox itself never gets the focus).
I like the solution proposed by DLeh in here: https://stackoverflow.com/a/24327765/352826 but I would like an improvement: Instead of having the behaviour calling a command on the base view model (the vm which contains the list of items), I would like the behaviour to directly act on the IsChecked property of the item.
My problem is that I don't know how to modify the behaviour or how to set up the binding on it, so that the behaviour can have access to the item's IsChecked property.
So, instead of the following:
<DataGrid>
<i:Interaction.Behaviors>
<shared:ToggleSelectOnSpace ToggleSelectCommand="{Binding Data.ToggleSelectParticipantCommand, Source={StaticResource BindingProxy}}" />
</i:Interaction.Behaviors>
...
</DataGrid>
I would have something like this:
<DataGrid>
<i:Interaction.Behaviors>
<shared:ToggleSelectOnSpace ItemsIsSelectedProperty="{Binding IsChecked}" />
</i:Interaction.Behaviors>
...
</DataGrid>
Update
I should add that my current implementation uses the PreviewKeyUp event in the itemscontrol and the following code behind implementation. The problem with this approach is that I have this code in many code behind files, so there is a lot of duplication. My goal is to replace this by a behaviour.
private void TreeView_OnPreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
var tree = (TreeView) sender;
var item = tree.SelectedItem as IsSelectedViewModelBase;
if (item != null)
{
item.IsSelected = !item.IsSelected;
e.Handled = true;
}
}
}
Update 2
This is the item template and the checkbox is not checked when you press the space bar with the item selected.
<DataTemplate DataType="{x:Type viewModels:ItemViewModel}" >
<StackPanel Orientation="Horizontal" >
<CheckBox Focusable="False" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" />
<StackPanel Margin="2">
<TextBlock Text="{Binding Username}" FontWeight="Bold" />
<TextBlock Text="{Binding FullName}" />
</StackPanel>
</StackPanel>
</DataTemplate>
Not an answer to the asked question per se, but the following will illustrate that it is possible for a check box within a list item to receive keyboard focus.
I created a new WPF project using the default Visual Studio template. This creates a single window called "MainWindow". Here's the contents of the XAML and code-behind of that window.
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thingies = new List<Thingy>
{
new Thingy { Name = "abc", IsChecked = false },
new Thingy { Name = "def", IsChecked = true },
new Thingy { Name = "ghi", IsChecked = false },
new Thingy { Name = "jkl", IsChecked = true },
new Thingy { Name = "mno", IsChecked = false },
}.ToArray();
DataContext = this;
}
public Thingy[] Thingies { get; private set; }
public class Thingy : INotifyPropertyChanged
{
public string Name { get; set; }
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked != value)
{
_isChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("IsChecked"));
}
Console.WriteLine(Name + " = " + _isChecked);
}
}
}
bool _isChecked;
public event PropertyChangedEventHandler PropertyChanged;
}
}
}
MainWindow.xaml
<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" Height="350" Width="525">
<ListBox ItemsSource="{Binding Thingies}">
<ListBox.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1" Width="400">
<TextBlock Text="{Binding Name}" />
<CheckBox IsChecked="{Binding IsChecked}" />
</UniformGrid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
Is that even possible? I have two ObservableCollections. I want to bind and populate a listbox with one of them. For example let's say that we have 2 buttons - one for Twitter and one for Facebook. Clicking on a Facebook button it will populate listbox with friend's names from facebook observable collection and it will bind it. Clicking on Twitter it will populate listbox with Twitter followers and populate listbox and bind it.
How to choose which collection will be populated in listbox?
I would just use one observable collection and fill based on the users choice. You could also fill it with the names from both sources and have a filter to filter out one or the other (apparently you need a wrapper object where you can indicate whether the name is a facebook friend or twitter follower).
Edit: Here is some quick code example of how you can do it:
public interface ISocialContact
{
string Name { get; }
}
public class FacebookContact : ISocialContact
{
public string Name { get; set; }
public string FacebookPage { get; set; }
}
public class TwitterContact : ISocialContact
{
public string Name { get; set; }
public string TwitterAccount { get; set; }
}
Then in your data context:
public ObservableCollection<ISocialContact> Contacts { get; set; }
...
Contacts = new ObservableCollection<ISocialContact> {
new FacebookContact { Name = "Face", FacebookPage = "book" },
new TwitterContact { Name = "Twit", TwitterAccount = "ter" }
};
And in your xaml:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:FacebookContact}">
<TextBlock Text="{Binding FacebookPage}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TwitterContact}">
<TextBlock Text="{Binding TwitterAccount}"/>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Contacts}" Width="100" Height="100"/>
</Grid>
This will apply the appropriate template to each object in your collection. So you can have collection with just facebook contacts or just twitter contacts or mixed.
Also note: You do not need the common interface. It will also work if you just make your ObservableCollection of type object. But given that they are being displayed by the same app in the same list box indicates that you can find some kind of common base and either can create a comon interface or base class.
In your ViewModel, create a property that exposes one or the other ObservableCollection, and swap it out when the button is clicked:
private ObservableCollection<string> _twitterFriendList;
private ObservableCollection<string> _facebookFriendList;
private ObservableCollection<string> _selectedFriendList;
public ObservableCollection<string> SelectedFriendList
{
get { return _selectedFriendList; }
set
{
if (value != _selectedFriendList)
{
_selectedFriendList = value;
RaisePropertyChanged("SelectedFriendList");
}
}
}
void TwitterButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _twitterFriendList;
}
void FacebookButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _facebookFriendList;
}
Then in your XAML you can just bind to the property:
<ListBox ItemsSource="{Binding SelectedFriendList}"/>
A non-elegant way of accomplishing this is to put 2 listboxes in the same location and bind 1 to the twitter collection and the other to the facebook collection. Bind their visibility to a property that changes based upon the button clicks. Personally, I'd have 2 radio buttons and display the listbox based upon which one is selected.
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=TwitterCollection}" SelectedItem="{Binding Path=SelectedTwitterItem}" Visibility="{Binding Path=IsTwitterSelected, Converter={StaticResource visibilityConverter}}" />
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=FacebookCollection}" SelectedItem="{Binding Path=SelectedFacebookItem}" Visibility="{Binding Path=IsFacebookSelected, Converter={StaticResource visibilityConverter}}" />
<RadioButton Grid.Row="1" Grid.Column="0" GroupName="rdoOptions" Content="{Binding Path=TwitterLabel}" IsChecked="{Binding Path=IsTwitterSelected}" />
<RadioButton Grid.Row="1" Grid.Column="1" GroupName="rdoOptions" Content="{Binding Path=FacebookLabel}" IsChecked="{Binding Path=IsFacebookSelected}" />