WPF MVVM ListBox.ItemTemplate CheckBox IsChecked binding - c#

The scenario I'm working with is editing Roles and Permissions. In a list box I want to list all defined Permissions and check the Permissions the selected Role has been assigned. The Role selection occurs in a separate list.
I have a simple view that contains a list box that displays all permissions:
<ListBox
...
ItemsSource="{Binding AllPermissions}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding DisplayName}"
IsChecked="???"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ItemsSource is one set of Permissions and the selected role's Permissions is a different set. How would I bind the IsChecked value to the intersection of the sets (i.e., if the Permission in the ListBox is also in the selected role's Permissions then the box should be checked)?

I'm guessing your Item source of AllPermissions is a collection of Permission objects. So just make sure that in addition to the DisplayName that it also has something on there that determines whether the role has the permission:
public class Permission : ViewModelBase
{
private string displayName;
private bool roleHasPermission;
public string DisplayName
{
get
{
return this.displayName;
}
set
{
this.displayName = value;
this.RaisePropertyChanged(() => this.DisplayName);
}
}
public bool RoleHasPermission
{
get
{
return this.roleHasPermission;
}
set
{
this.roleHasPermission = value;
this.RaisePropertyChanged(() => this.RoleHasPermission);
}
}
}
so then Bind IsChecked to RoleHasPermission.
Now I'm guessing at the moment that you are loading the available permissions from somewhere and they are currently ignorant of if the role has thepermission, so when you are loading the AllPermissions, calculate whether the rolehas the permission.
I have assumed you have inherited from a base class that has a RaisePropertyChanged event on it to notify the view when the value has been updated. (such as provided for you if you use mvvm light or other frameworks, or you can write your own) Also if you want to be able to edit the permission by checking/unchecking the check box, then remember to set the binding Mode=TwoWay ie:
<ListBox
...
ItemsSource="{Binding AllPermissions}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding DisplayName}"
IsChecked="{Binding RoleHasPermission, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>

I would try using the Converter property of the binding for IsChecked. The ConverterParameter for the binding would be set to the list of permissions for the selected role.
I don't know how familiar you are with ValueConverters, but I could write some code to illustrate if that would help. ValueConverter examples are easy to find on the internet. The first time I ever implemented a ValueConverter, I followed this example from David Anson's blog.

I would write a ViewModel for your Role that has a collection of Permissions.
public class PermissionViewModel : ViewModelBase
{
public string Name { get; set; }
public bool HasPermission { get; set; }
}
public class RoleViewModel : ViewModelBase
{
public string Name { get; set; }
public ObservableCollection<PermissionViewModel> PermissionViewModels { get; set; }
}
<ListBox
...
ItemsSource="{Binding SelectedRoleViewModel.PermissionViewModels}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"
IsChecked="{Binding HasPermission, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>

Related

Is it possible to bind the MenuItem.IsEnabled property to a different Context?

I have a ListView bound to a collection of objects (called Users, in this case), and the template includes a ContextActions menu. One of the menu items needs to be enabled or disabled depending on a condition having nothing directly to do with the items in the view (whether or not there's a Bluetooth connection to a certain kind of peripheral). What I'm doing right now is iterating the Cells in the TemplatedItems property and setting IsEnabled on each.
Here's the XAML for the ListView, stripped down to the parts that matter for my question:
<ListView ItemsSource="{Binding .}" ItemTapped="item_Tap">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Label}">
<TextCell.ContextActions>
<MenuItem
Text="Copy to other device"
ClassId="copyMenuItem"
Clicked="copyMenuItem_Click" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's how I'm setting the property values now:
foreach (Cell cell in usersListView.TemplatedItems)
{
foreach (MenuItem item in cell.ContextActions)
{
if ("copyMenuItem" == item.ClassId)
{
item.IsEnabled = isBluetoothConnected;
}
}
}
That works, but i don't like it. It's obviously out of line with the whole idea of data-bound views. I'd much rather have a boolean value that I can bind to the IsEnabled property, but it doesn't make sense from an object design point of view to add that to the User object; it has nothing to do with what that class is about (representing login accounts). I thought of wrapping User in some local class that exists just to tape this boolean property onto it, but that feels strange also since the value will always be the same for every item in the collection. Is there some other way to bind the MenuItem.IsEnabled property?
Use relative binding
Get ready in your view model class, inherit INotifyPropertyChanged or your BaseViewModel.
public class YourViewModel : INotifyPropertyChanged
{
private string isBluetoothConnected;
public string IsBluetoothConnected
{
get => isBluetoothConnected;
set => SetProperty(ref isBluetoothConnected, value);
}
public ObservableCollection<User> Users { get; private set; }
}
Add a name to ListView for reference, and apply relative binding in MenuItem.
<ListView
x:Name="UserListView"
ItemsSource="{Binding Users}"
ItemTapped="item_Tap">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Label}">
<TextCell.ContextActions>
<MenuItem
IsEnabled="{Binding Path=BindingContext.IsBluetoothConnected, Source={x:Reference UserListView}}"
Text="Copy to other device"
ClassId="copyMenuItem"
Clicked="copyMenuItem_Click" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
It turns out that this case of BindableProperty is, in fact, not bindable: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/menuitem#enable-or-disable-a-menuitem-at-runtime
One must add a Command property to the MenuItem and assign a BindingContext to that, and set its executability. Here's the latest version of my code, which does work:
<MenuItem
Text="Copy to other device"
Clicked="copyMenuItem_Click"
BindingContext="{x:Reference usersListView}"
Command="{Binding BindingContext.CopyCommand}" />
public class UsersViewModel
{
public Command CopyCommand { get; set; }
public bool IsBluetoothConnected
{
get { return isBluetoothConnected; }
set
{
isBluetoothConnected = value;
if (CopyCommand.CanExecute(null) != value)
{
CopyCommand.ChangeCanExecute();
}
}
}
public ObservableCollection<User> Users { get; private set; }
private bool isBluetoothConnected;
public async System.Threading.Tasks.Task<int> Populate( )
{
CopyCommand = new Command(( ) => { return; }, ( ) => IsBluetoothConnected); // execute parameter is a no-op since I really just want the canExecute parameter
IList<User> users = await App.DB.GetUsersAsync();
Users = new ObservableCollection<User>(users.OrderBy(user => user.Username));
return Users.Count;
}
}
I'm still not entirely happy with this; it contaminates the view model with the concerns of a specific view. I'm going to see if I can separate the Command from the view model. But it does accomplish my primary goal, bringing this UI implementation into the data binding paradigm.

Converting a ComboBox to an AutoCompleteBox using the WPF Toolkit

I'm having a bit of trouble to achieve the conversion of a "complex" ComboBox to an equally complex AutoCompleteBox. My goal is to be able to select and set a ShoppingCart's Item to be like one of the Items of a list. Here's the three steps to take to reproduce my situation (I'm using Stylet and its SetAndNotify() INPC method):
Create two objects, one having only a Name property and the other one having only the other object as a property
public class ItemModel : PropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => SetAndNotify(ref _name, value);
}
}
public class ShoppingCartModel : PropertyChangedBase
{
public ItemModel Item { get; set; }
}
initialize and Populate both the ItemsList and the Shoppingcart in the DataContext (since we're using MVVM, it's the ViewModel)
public ShoppingCartModel ShoppingCart { get; set; }
public ObservableCollection<ItemModel> ItemsList { get; set; }
public ShellViewModel()
{
ItemsList = new ObservableCollection<ItemModel>()
{
new ItemModel { Name = "T-shirt"},
new ItemModel { Name = "Jean"},
new ItemModel { Name = "Boots"},
new ItemModel { Name = "Hat"},
new ItemModel { Name = "Jacket"},
};
ShoppingCart = new ShoppingCartModel() { Item = new ItemModel() };
}
Create the AutoCompleteBox, ComboBox, and a small TextBlock inside the View to test it all out:
<Window [...] xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=DotNetProjects.Input.Toolkit">
<!-- Required Template to show the names of the Items in the ItemsList -->
<Window.Resources>
<DataTemplate x:Key="AutoCompleteBoxItemTemplate">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Background="Transparent">
<Label Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<!-- AutoCompleteBox: can see the items list but selecting doesn't change ShoppingCart.Item.Name -->
<Label Content="AutoCompleteBox with ShoppingCart.Item.Name as SelectedItem:"/>
<toolkit:AutoCompleteBox ItemsSource="{Binding ItemsList}"
ValueMemberPath="Name"
SelectedItem="{Binding Path=ShoppingCart.Item.Name}"
ItemTemplate="{StaticResource AutoCompleteBoxItemTemplate}"/>
<!-- ComboBox: can see the items list and selecting changes ShoppingCart.Item.Name value -->
<Label Content="ComboBox with ShoppingCart.Item.Name as SelectedValue:"/>
<ComboBox ItemsSource="{Binding ItemsList}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=ShoppingCart.Item.Name}"
SelectedValuePath="Name"
SelectedIndex="{Binding Path=ShoppingCart.Item}" />
<!-- TextBox: Typing "Jean" or "Jacket" updates the ComboBox, but not the AutoCompleteBox -->
<Label Content="Value of ShoppingCart.Item.Name:"/>
<TextBox Text="{Binding Path=ShoppingCart.Item.Name}"/>
</StackPanel>
</window>
Changing the Binding Mode of the AutoCompleteBox's SelectedItem to TwoWay makes it print "[ProjectName].ItemModel" which means (I guess?) it's getting ItemModels and not strings, but I can't seem to make it work. Any help will be appreciated, thanks and feel free to edit my post if it's possible to make it smaller.
After a lot of attempts, I finally found the culprits :
INPC not implemented in ShoppingCartModel.Item despite the PropertyChangedBase inheritance (either implementing INPC or remove the PropertyChangedBase inheritance work)
public class ShoppingCartModel : PropertyChangedBase
{
private ItemModel _item;
public ItemModel Item
{
get => _item;
set => SetAndNotify(ref _item, value);
}
}
AutoCompleteBox's SelectedItem must be of the same type of ItemsSource, and have a TwoWay Mode Binding
<toolkit:AutoCompleteBox ItemsSource="{Binding ItemsList}"
ValueMemberPath="Name"
SelectedItem="{Binding Path=ShoppingCart.Item, Mode=TwoWay}"
ItemTemplate="{StaticResource AutoCompleteBoxItemTemplate}"/>
And finally... the most mysterious one is the ComboBox! Simply by being there it messes with the AutoCompleteBox and I have no idea why, just commenting the whole ComboBox makes it all work. If you know why the ComboBox breaks the AutoCompleteBox's binding feel free to help.
There's another problem though, when using an AutoCompleteBox inside a ListView, but it's better to create a separate question for that issue here

How to get specific item listbox C# MVVM WPF

Hi i try to get specific item from a listbox. I try to bind but i get crash. Using Prism framework how can i bind to get specific item from listbox, what template i need to make. This is test code:
<ListBox SelectedItem="{Binding SelectIndex}" HorizontalAlignment="Left" Height="297" Margin="57,41,0,0" VerticalAlignment="Top" Width="681">
<ListBoxItem>
<TextBlock Text="Test123"/>
</ListBoxItem>
<ListBoxItem>
<TextBlock Text="Test123"/>
</ListBoxItem>
</ListBox>
C# code:
public int SelectIndex
{
get
{
return 1;
}
}
How can i get if i want specific item from this list? What variable type need to make for binding listbox to select items?
It's crashing because you are binding SelectedItem (of type object) to your SelectIndex property (int property) in your view model (VM). ListBox like many WPF controls have distinct SelectedIndex and SelectedItem properties for binding.
If you want to bind to an int property in order to get the index you should be binding the ListBox's SelectedIndex property instead.
Change:
<ListBox SelectedItem="{Binding SelectIndex}" ...
...to:
<ListBox SelectedIndex="{Binding SelectIndex}" ...
Your VM remains as:
public int SelectIndex { get { return 1; } }
Though to be more useful and allow users to choose different items it should be:
public int SelectIndex { get; set; } // TODO: add support for INotifyPropertyChanged
Optionally you can add:
<ListBox SelectedIndex="{Binding SelectIndex}"
SelectedItem="{Binding SelectedItem}" ...
ViewModel:
public int SelectIndex { get; set; } // TODO: add support for INotifyPropertyChanged
// replace object with your type
public object SelectedItem { get; set; } // TODO: add support for INotifyPropertyChanged

How to create a group of Controls based on properties of a model class

I'm writing a desktop application which contains some user permission management, and therefore I'm building a segment to manage all the user permissions.
Before I start hardcoding all the different types of permissions into the UI, I was wondering if it is possible, to let WPF do this dynamically for me.
To be more specific, I have a model with the user and his permissions (each User has a Role) which looks like this:
public class Role
{
public enum Permission
{
None,
Read,
Write
};
public int id;
public string name;
public bool isAdmin;
public Permission Usermanagement;
public Permission Appointments;
public Permission Events;
and for each Permission in this class I want WPF to create a Template with the name and a comboBox to select the kind of permission.
My first guess was to use a ListView/ListBox with a Template (that part is not the problem).
My question is how can I get the list of permissions as a source list for my Template, and afterwards how to bind the real object to it, so the right permission gets updated.
This is more a question about possibility, I could just hardcode my permissions and bind them one-by-one.
You may use a collection of name/permission pairs like this:
public class NamedPermission
{
public string Name { get; set; }
public Permission Permission { get; set; }
}
public class Role
{
...
public List<NamedPermission> Permissions { get; } = new List<NamedPermission>
{
new NamedPermission { Name = "Usermanagement" },
new NamedPermission { Name = "Appointments" },
new NamedPermission { Name = "Events" }
};
}
and bind an ItemsControl like this:
<ItemsControl ItemsSource="{Binding Permissions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Width="100" VerticalAlignment="Center"/>
<ComboBox SelectedValuePath="Content" SelectedValue="{Binding Permission}">
<ComboBoxItem Content="None"/>
<ComboBoxItem Content="Read"/>
<ComboBoxItem Content="Write"/>
</ComboBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Dynamically grouping/nesting flat data in a TreeView

I would like to take a flat list of objects and present them in a TreeView using custom groups.
public enum DocumentType { Current, Inactive, Transition, Checkpack, TechLog, Delivery }
public enum Status { Approved, Rejected, Pending }
public class Document
{
public string Name { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
public DateTime Modified { get; set; }
public string ModifiedBy { get; set; }
public DocumentType Type { get; set; }
public Status Status { get; set; }
}
For example... The user might want to see this list, with the top level group being "Status" and the second level being "Name". This all needs to be configurable from the UI, and I'm struggling to find the best way to achieve it.
I've had a brief look at the CollectionViewSource object, but couldn't find a good way to get it to dynamically build a TreeView.
My gut feeling is that i'll need to do some clever templating in XAML - this is as far as i've got...
<Window.Resources>
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding Name}" />
</DockPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="GroupTemplate"
ItemsSource="{Binding Path=Items}"
ItemTemplate="{StaticResource DocumentTemplate}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Documents.View.Groups}"
ItemTemplate="{StaticResource GroupTemplate}"/>
</Grid>
public CollectionViewSource Documents
{
get
{
var docs = new CollectionViewSource();
docs.Source = DocumentFactory.Documents;
docs.GroupDescriptions.Add(new PropertyGroupDescription("CreatedBy"));
return docs;
}
}
Of course this only displays the Top-level group ("CreatedBy").
After reading a question below, I managed to come up with a better question...
My question: Is it possible to have a generic HierarchicalDataTemplate for a TreeView that displays custom groups applied to a CollectionViewSource.
Honestly this should be marked as a bug in WPF. I too tried and found that Documents.View.Groups throws binding error on View property being null.
Also
<TextBlock Text="{Binding Path=Name}" />
is correct in the GroupTemplate but not in the DocumentTemplate. Note that Groups are of special type GroupItem where Name is one such property that holds the value on which grouping has taken place.
On the other hand in DocumentTemplate, we should refer the property that we need to display on the leaf nodes items e.g. in my example I used Employee.FirstName (I grouped on Gender).
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding FirstName}" />
</DockPanel>
</DataTemplate>
Now for binding to take effect I had to introduce a converter which simply returns Groups.
public class GroupsConverter : IValueConverter
{
public object Convert(object value, ...)
{
return ((CollectionViewSource)value).View.Groups;
}
....
}
And tree view binding was changed this way...
<TreeView x:Name="treeView"
ItemsSource="{Binding Path=Documents,
Converter={StaticResource GroupsConverter}}"
ItemTemplate="{StaticResource GroupTemplate}" />
Then this worked for me.
Does this help you?

Categories

Resources