Bind combobox inside an itemscontrol - c#

I am trying to populate a combobox, which is part of an itemscontrol, with a list of items (ParentCredentials). The problem is that these ParentCredentials are at the same level of the items being binded with the itemscontrol. Not sure if this is clear but if you have a look at the view model it should make more sense
This is my viewmodel:
public class AccessControlViewModel : INotifyPropertyChanged
{
public ObservableCollection<LogonCredential> Credentials
{...}
public List<string> ParentCredentials
{...}
}
And i have the following XAML.
<ItemsControl ItemsSource="{Binding AccessControl.Credentials}" HorizontalContentAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=DisplayName}"/>
<ComboBox Grid.Column="2" ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type vm:ResourceViewModel}}, Path=AccessControl.ParentCredentials}">
</ComboBox>
...
How can I make this binding? Also note that AccessControl is a part of ResourceViewModel class.

You need to navigate back up to the ItemsControl, and bind via the DataContext path.
{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}, Path=DataContext.AccessControl.ParentCredentials}
Source={RelativeSource... never works in any case. Further the AncestorType is always some FrameworkElement rather than a data-object.

Related

Multiselect listbox with checkbox in WPF

I have a WPF project and I would like to have a listbox with checkbox on each ListboxItem and have an ObservableCollection to store checked ListboxItems.
I need to move checked chars from one ObservableCollection to another ObservableCollection.
With this code I can select multiple checkboxes but when I trigger the command MoveChar (command to move chars to another ObservableCollection) via the button only one ListboxItem moves and I need to click it more times to move all the checked chars.
View
<ListBox SelectionMode="Multiple" ItemsSource="{Binding Chars}" SelectedIndex="{Binding SelectedCharsIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedChars, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Char}" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Description}" Grid.Column="1" HorizontalAlignment="Left"/>
<CheckBox Grid.Column="2" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel
public CharModel SelectedChars {
get { return _selectedChars; }
set { _selectedChars = value; NotifyPropertyChanged(); }
}
MoveChar = new RelayCommand(
() =>
{
if (SelectedChars != null)
{
TestChars.Add(SelectedChars);
Chars.Remove(SelectedChars);
Selected = false;
}
});
When I change SelectedChars to ObservableCollection<CharModel> it doesn´t work at all. In ViewModel I iterate each item via foreach.
You are binding SelectedChars to the SelectedItem property which is for use with Single selection listboxes and will only return a single object, not a collection. Try binding to the SelectedItems property. Be sure the "Items" part is plural. You will need to refactor the property type to be a SelectedObjectCollection as well but this inherits IList so you can iterate the same.

Change type of control based on type of item source?

I am working with MVVM design pattern where I want to change the type of control to be used based on the type of item source object.
My code for item source types :
public class PropertyItemVM : ViewModelBase
{
// Some base code for all basic property items
}
public class StringValuePropertyItemVM : PropertyItemVM
{
// Code to correctly create string value property item
}
public class ComboBoxPropertyItemVM : PropertyItemVM
{
// Code to correctly create combo box property item
}
In my view model I have property list binded to xaml as
public ObservableCollection<PropertyItemVM> Properties { get; set; }
This collection contains both the types of objects StringValuePropertyItemVM and ComboBoxPropertyItemVM.
Now what I want to achieve is, based on type of object I want to decide whether the xaml will contain a TextBox or a ComboBox.
My xaml code :
<StackPanel>
<!--Items Control containing all list of properties to be shown-->
<ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Label}"
IsEnabled="{Binding IsEnabled}"
Grid.Column="0"/>
<!-- Here based on type need to change the TextBox with ComboBox-->
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
IsEnabled="{Binding IsEnabled}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
With some help I found out I can have different data templates for both types and switch among those, however I can't find how to do that. Can anyone help me with how can this be achieved ?
Automatic Data Template Selection
Move the data templates to the ItemsControl resources and assign a DataType.
<StackPanel>
<!--Items Control containing all list of properties to be shown-->
<ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:StringValuePropertyItemVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Label}"
IsEnabled="{Binding IsEnabled}"
Grid.Column="0"/>
<!-- Here based on type need to change the TextBox with ComboBox-->
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
IsEnabled="{Binding IsEnabled}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ComboBoxPropertyItemVM}">
<ComboBox/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</StackPanel>
The ItemsControl will automatically select the right data template based on the type.
Data Template Selector
Although you do not need it in this case, for more complex cases (e.g. conditions based on the view model), you could implement a DataTemplateSelector, like this:
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (!(container is FrameworkElement frameworkElement))
return null;
switch (item)
{
case StringValuePropertyItemVM _:
return (DataTemplate)frameworkElement.FindResource("StringValuePropertyItemVMDataTemplate");
case ComboBoxPropertyItemVM _:
return (DataTemplate)frameworkElement.FindResource("ComboBoxPropertyItemVMDataTemplate");
default:
return null;
}
}
}
Then you would define the data templates in a resource dictionary in scope with keys.
<DataTemplate x:Key="StringValuePropertyItemVMDataTemplate" DataType="{x:Type local:StringValuePropertyItemVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Label}"
IsEnabled="{Binding IsEnabled}"
Grid.Column="0"/>
<!-- Here based on type need to change the TextBox with ComboBox-->
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
IsEnabled="{Binding IsEnabled}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ComboBoxPropertyItemVMDataTemplate" DataType="{x:Type local:ComboBoxPropertyItemVM}">
<ComboBox/>
</DataTemplate>
Finally, you would assign or reference an instance of the selector to the ItemsControl.
<StackPanel>
<!--Items Control containing all list of properties to be shown-->
<ItemsControl x:Name="Local" ItemsSource="{Binding Properties}" Background="White">
<ItemsControl.ItemTemplateSelector>
<local:PropertyDataTemplateSelector/>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
</StackPanel>

Command binding for button in ItemsControl in WPF NotifyIcon

I'm using hardcodet's WPF NotifyIcon in my app, which is being implemented using MVVM Light. I'm trying to create a custom TrayPopup for the icon. In this popup I have an ItemsControl, whose ItemsSource is a list of objects "NumberList" in my ViewModel. For my DataTemplate I'm using a grid which display an property called "Id" of my source object, and a button. The button I'm trying to tie back to a RelayCommand in my ViewModel. I've done similar things with ItemsControls in the past by Binding using a RelativeSource to get the DataContext of the Window Ancestor. I tried a similar approach this time, but when I run the app, open the TrayPopup, and click the button nothing happens. My command is not fired in my ViewModel.
As a test I tried adding another button to my TrayPopup and binding its command to the same one in my ViewModel. That binding works fine, so it's not the command as far as I can tell.
Like I said, I've used this technique almost exactly in the past, just never before in a NotifyIcon. I've tried specifying the ElementName in the button Binding to the name of my Window, which didn't work. I've also tried changing the RelativeSource to the parent TaskbarIcon, also without success.
Here is an example implementation of what the NotifyIcon with the same problem:
<tb:TaskbarIcon PopupActivation="LeftOrRightClick">
<tb:TaskbarIcon.TrayPopup>
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0">
<ItemsControl ItemsSource="{Binding NumberList, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Id, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1" Content="Test" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.TestCommand}"/> <!--This binding doesn't work!-->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<Button Grid.Row="1" Content="Test2" Command="{Binding TestCommand}"/> <!--This works-->
</Grid>
</tb:TaskbarIcon.TrayPopup>
</tb:TaskbarIcon>
I'm fairly confident everything in the ViewModel is correct as I have been using a previous version of this app that I wrote a while ago. So far I haven't run in to any problems other than this NotifyIcon. Any ideas?
After banging away at it for a while I found an answer to my problem. The StackPanel in my ItemsPanelTemplate had the DataContext which contained the command I wanted to bind my button to, my main ViewModel. By specifying a name for the StackPanel, I was able to set the ElementName of my button to its name, and then the binding worked. Not sure why this method worked with the StackPanel and not the main Window though.
Anyway, here's the updated code if anyone runs in to this same problem:
<tb:TaskbarIcon PopupActivation="LeftOrRightClick">
<tb:TaskbarIcon.TrayPopup>
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0">
<ItemsControl ItemsSource="{Binding NumberList, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Id, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1" Content="Test" Command="{Binding ElementName=trayMainStack, Path=DataContext.TestCommand}"/> <!--Updated binding works!-->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="trayMainStack"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<Button Grid.Row="1" Content="Test2" Command="{Binding TestCommand}"/> <!--This works-->
</Grid>
</tb:TaskbarIcon.TrayPopup>
</tb:TaskbarIcon>

How do you reference the matching object from a DataTemplate in XAML?

I want to use CommandParameter attribute in a context menu associated to a DataTemplate. The commandParameter should contain a reference to the object that triggered the data template as shown in the code sample below. I tried to use "{Binding Path=this}" but it does not work because "this" is not a property. The command fires but I can't get the right parameter. Do any one have an idea on how to do this?
Note: I removed Command="{Binding DeleteSelectedMeetingCommand}" by replacing it with a reference to the view locator and the command was triggering.
<DataTemplate DataType="{x:Type Models:MeetingDbEntry}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=HostTeam}"/>
<TextBlock Grid.Column="1" Text="{Binding Path=GuestTeam}"/>
<TextBlock Grid.Column="2" Text="{Binding Path=Result}"/>
<Grid.ContextMenu>
<ContextMenu Name="MeetingMenu">
<MenuItem Header="Delete"
Command="{Binding
Source={StaticResource Locator},
Path=Main.DeleteSelectedMeetingCommand}"
CommandParameter="{Binding Path=this}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
Thanks,
It is working with the code below. You just need to type {Binding} in the CommandParameter attribute in order to reference the property that triggered the DataTemplate.
<DataTemplate DataType="{x:Type Models:MeetingDbEntry}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=HostTeam}"/>
<TextBlock Grid.Column="1" Text="{Binding Path=GuestTeam}"/>
<TextBlock Grid.Column="2" Text="{Binding Path=Result}"/>
<Grid.ContextMenu>
<ContextMenu Name="MeetingMenu">
<MenuItem Header="Delete"
Command="{Binding
Source={StaticResource Locator},
Path=Main.DeleteSelectedMeetingCommand}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
I'd expose the DeleteSelectedMeetingCommand on the object it deletes and bind the context menu entry to it. Then add a member variable holding the object to delete to the command and initialize it with this in the object to delete that's holding the command.
Example:
public class DeletableObject
{
public ICommand DeleteCommand { get; }
public DeleteableObject()
{
DeleteCommand = new DeleteCommand(this);
}
}
public class DeleteCommand : ICommand
{
private DeletableObject _DeletableObject;
public DeleteCommand(DeletableObject deletableObject)
{
_DeletableObject = deletableObject;
}
// skipped the implementation of ICommand but it deletes _DeletableObject
}
Hope that helps.

CollectionViewSource Use Question

I am trying to do a basic use of CollectionViewSource and I must be missing something because it is just not working. Here is my XAML:
<Window.Resources>
<CollectionViewSource Source="{Binding loc:MainVM.Instance.MapItems}" x:Key="MapCV">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="SourceProject" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<ListBox ItemsSource="{StaticResource MapCV}" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding SourceType, Converter={StaticResource WorkItemTypeToStringConverter}}"/>
<ComboBox Grid.Column="1" SelectedItem="{Binding DestType}" ItemsSource="{Binding WorkItemTypesForCurrentDestProject, Source={x:Static loc:MainMediator.Instance}, diagnostics:PresentationTraceSources.TraceLevel=High}" DisplayMemberPath="Name" />
<Button Grid.Column="2" Content="{Binding PercentMapped}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This compiles fine, but when I run the app I get this error:
Cannot convert the value in attribute 'ItemsSource' to object of type
'System.Collections.IEnumerable'. 'System.Windows.Data.CollectionViewSource'
is not a valid value for property 'ItemsSource'. Error at object
'System.Windows.Controls.ListBox' in markup file 'WIAssistant;component/main.xaml
This is the collection I am attaching to:
// The mappings used to copy the values of the fields of one WorkItem to another.
public ObservableCollection<WorkItemTypeMapping> WorkItemTypeMappings
{
get { return (ObservableCollection<WorkItemTypeMapping>)
GetValue(WorkItemTypeMappingsProperty); }
set { SetValue(WorkItemTypeMappingsProperty, value); }
}
public static readonly DependencyProperty WorkItemTypeMappingsProperty =
DependencyProperty.Register("WorkItemTypeMappings",
typeof(ObservableCollection<WorkItemTypeMapping>), typeof(MainMediator),
new UIPropertyMetadata(null));
I just want to do simple grouping on object Project SourceProject. I would rather not have to break out a tree view for this.
This should work for you
<ListBox ItemsSource="{Binding Source={StaticResource MapCV}}" ...

Categories