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.
Related
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>
I have a TabControl in my app. I'd like to have as many TabItems as many entries are in my dictionary.
Here's my dictionary:
public Dictionary<string , ObservableCollection<PerformanceCounter>> Counters
{
get { return _Counters; }
}
Dictionary<string, ObservableCollection<PerformanceCounter>> _Counters = new Dictionary<string , ObservableCollection<PerformanceCounter>>();
Every entry has a string key and ObservableCollection of PerformanceCounter objects. Important thing is the fact that every PerformanceCounter object has properties: CounterName and InstanceName - I'll need these two to display them.
Now, to my XAML:
<TabItem Header="Memory">
<Grid Name="RAMGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Name="RAMListBox" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" ItemsSource="{Binding Memory, Mode=OneWay}" SelectionMode="Multiple" BorderThickness="1" BorderBrush="#FF8B8B8B" SelectionChanged="RAMListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding CounterName, Mode=OneWay}" />
<Run Text="{Binding InstanceName, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="RAMSelectAllButton" Margin="0,10,0,0" Grid.Column="0" Grid.Row="1" Click="RAMSelectAllButton_Click" >
<TextBlock Text="SELECT ALL"/>
</Button>
<Button Name="RAMUnSelectAllButton" Margin="0,10,0,0" Grid.Column="1" Grid.Row="1" Click="RAMUnSelectAllButton_Click" >
<TextBlock Text="UNSELECT ALL"/>
</Button>
</Grid>
</TabItem>
That's what I did and, as you might already know, it does not work. The above code is only for one entry of my dictionary, where the key is "Memory".
In my code I set DataContext:
this.DataContext = appData.Counters;
appData.Counters is that dictionary I presented at the beginning.
Here's what I'd like to achieve:
No matter how many entries there are in my dictionary, my TabControl would display TabItem for each of them.
Each TabItem has a ListBox and 2 buttons. I'll need too be able to access those (in order to clear the list and to have click event for each button).
I really don't know how to do it, I hope you can help me out.
Binding TabControl to items in Dictionary:
<Window.Resources>
<DataTemplate x:Key="templateForTheContent" >
<StackPanel>
<ListBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0"
ItemsSource="{Binding Value, Mode=OneWay}"
SelectionMode="Multiple"
BorderThickness="1" BorderBrush="#FF8B8B8B">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding CounterName, Mode=OneWay}" />
<Run Text="{Binding InstanceName, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="templateForTheHeader" >
<TextBlock Text="{Binding Key}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl TabStripPlacement="Left" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Counters}"
ContentTemplate="{StaticResource templateForTheContent}"
ItemTemplate="{StaticResource templateForTheHeader}">
</TabControl>
</Grid>
Now, Dictionary is not observable so if items will be added/removed during runtime, you may consider using something like ObservableDictionary instead
Create a ViewModel-class containing:
your Dictionary
two ICommand-Implementations for your Buttons
then
set the ViewModel-class as DataContext of the TabControl
set Counters as the ItemSource of the TabControl
reuse your XAML-Code defined within the TabItem and use it as
the Tabcontrol.ContentTemplate
Bind .Command of your Buttons to the ICommands in your ViewModel using RelativeSource
see for samples:
ContentTemplate: https://wpf.2000things.com/tag/tabcontrol/
ICommand https://stackoverflow.com/a/1468830/4919708
RelativeSource: https://stackoverflow.com/a/84317/4919708
As i said in one of the comments above I changed my Dictionary to this:
//list of all counters
public ObservableCollection<ObservableCollection<PerformanceCounter>> Counters
{
get { return _Counters; }
}
ObservableCollection<ObservableCollection<PerformanceCounter>> _Counters = new ObservableCollection<ObservableCollection<PerformanceCounter>>();
i used #Arie's solution to write this XAML:
<DataTemplate x:Key="templateForTheContent" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0"
ItemsSource="{Binding}"
SelectionMode="Multiple"
BorderThickness="1" BorderBrush="#FF8B8B8B">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding CounterName, Mode=OneWay}" />
<Run Text="{Binding InstanceName, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="RAMSelectAllButton" Margin="0,10,0,0" Grid.Column="0" Grid.Row="1" >
<TextBlock Text="SELECT ALL"/>
</Button>
<Button Name="RAMUnSelectAllButton" Margin="0,10,0,0" Grid.Column="1" Grid.Row="1" >
<TextBlock Text="UNSELECT ALL"/>
</Button>
</Grid>
</DataTemplate>
<DataTemplate x:Key="templateForTheHeader" >
<TextBlock Text="{Binding CategoryName}"/>
</DataTemplate>
</Window.Resources>
It displays correctly as many Tabs as I add entries to my Class ObservableCollection in the code behind.
Now i have a new problem: I don't know how to access each listBox from each Tab. i need to be able to read the list of selected objects.
I have a <Checkbox/> in my <GridView.ItemTemplate>. How do I handle the <Checkbox/> as to the element in which it is?
For example, I want to delete item when checkbox checked.
I think should write here. But what?
private void CheckBox_Checked_1(object sender, RoutedEventArgs e)
{
}
Here's My XAML:
<GridView Margin="0,10,0,0"
RelativePanel.AlignHorizontalCenterWithPanel="True"
x:Name="GridColections"
IsItemClickEnabled="True"
SelectionMode="None"
ItemsSource="{x:Bind DS.AllRem, Mode=OneWay}"
ItemClick="GridColections_ItemClick" >
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:GetRem" >
<Grid Margin="-2,0,-6,0" BorderBrush="LightGray" BorderThickness="1" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<TextBlock TextTrimming="CharacterEllipsis" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{x:Bind ReminderName}" Margin="5,5,0,0" FontSize="20"/>
<TextBlock TextTrimming="CharacterEllipsis" Grid.Column="0" Grid.Row="1" Width="600" TextWrapping="Wrap" Text="{x:Bind ReminderDescription}" Margin="5,5,0,0" FontSize="12"/>
<CheckBox Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Center" Checked="CheckBox_Checked_1"/>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The problem is that you almost certainly want to be able to use the DataContext in your click handler but you won't get that easily by just having a reference to the CheckBox which will be the sender argument in your callback. Normally what you would do here is create a Command on your item's view model and bind to that and any additional information that you would want to pass in you would pass in through the CheckBox's CommandParameter.
Once you do this you are now operating in your view model with a reference to whatever piece of information that you need through the command parameter (for instance you could set CommandParameter = "{Binding}" to pick up the entire data context which would be the item's view model and that would be accessible from your Command as an argument to it). You should be able to solve your issue this way.
I'm using WPFLocalizationExtension to localize a C#/ .Net4.5 application but I didn't manage to localize DropDown Menus with custom DataTemplate because I can't use the DisplayMemberPath. For ordinary dropdown localization works like this:
<telerik:RadComboBox ItemsSource="{Binding GlassColors}"
SelectedValue="{Binding Ampule.ID_GlassColor, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedValuePath="ID_GlassColor"
DisplayMemberPath="{lang:Loc dmp_GlassColor}"/>
The ComboBox example above is linked to a database table containing to language. The displayed language is changed by the localized DisplayMemberPath. This approach is very easy and I can recommend it to everyone else. How ever it doesn't work for comboboxes using a custom DataTemplate. See example below:
<telerik:RadComboBox ItemsSource="{Binding PackagingTypesFilter}"
SelectedValue="{Binding SelectedPackagingTypeFilter}"
SelectedValuePath="ID_PackagingType">
<telerik:RadComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type model:Tbl_PackagingMaster_ID_PackagingType}">
<Grid VerticalAlignment="Center" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding TypeIcon, Converter={StaticResource StringToUriConverter}}" VerticalAlignment="Top" Grid.Column="0" Height="14" Margin="2" />
<TextBlock Text="{Binding PackagingTypeDescription}" Style="{StaticResource TextBlockMediumSmallBlackStyle}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</telerik:RadComboBox.ItemTemplate>
</telerik:RadComboBox>
The ItemSource is a Collection where PackagingTypeDescription contains the English and PackagingTypeDescriptionGerman contains the German description.
How can I localize the code sample above?
I solved this using a TemplateSelector.
Here is the TemplateSelector class:
using System.Windows;
using System.Windows.Controls;
namespace Common.TemplateSelector
{
public class LanguageTemplateSelector : DataTemplateSelector
{
public DataTemplate TemplateEnglish { get; set; }
public DataTemplate TemplateGerman { get; set; }
public const string LanguageIdentifier = "de";
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return Configuration.Configuration.Language == LanguageIdentifier ? this.TemplateGerman : this.TemplateEnglish;
}
}
}
And here the template definition and the definition of the TemplateSelector defined in the <UserControl.Resources></UserControl.Resources> area:
<DataTemplate DataType="{x:Type model:Tbl_PackagingMaster_ID_PackagingType}" x:Key="PackagingTypeEnglish">
<Grid VerticalAlignment="Center" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding TypeIcon, Converter={StaticResource StringToUriConverter}}" VerticalAlignment="Top" Grid.Column="0" Height="14" Margin="2" />
<TextBlock Text="{Binding PackagingTypeDescription}" Style="{StaticResource TextBlockMediumSmallBlackStyle}" Grid.Column="1"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type model:Tbl_PackagingMaster_ID_PackagingType}" x:Key="PackagingTypeGerman">
<Grid VerticalAlignment="Center" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding TypeIcon, Converter={StaticResource StringToUriConverter}}" VerticalAlignment="Top" Grid.Column="0" Height="14" Margin="2" />
<TextBlock Text="{Binding PackagingTypeDescriptionGerman}" Style="{StaticResource TextBlockMediumSmallBlackStyle}" Grid.Column="1"/>
</Grid>
</DataTemplate>
<templateSelector:LanguageTemplateSelector x:Key="PackagingTypeLanguageSelector"
TemplateEnglish="{StaticResource PackagingTypeEnglish}"
TemplateGerman="{StaticResource PackagingTypeGerman}" />
This is how I use it for the ComboBox:
<telerik:RadComboBox ItemsSource="{Binding PackagingTypesFilter}"
SelectedValue="{Binding SelectedPackagingTypeFilter}"
SelectedValuePath="ID_PackagingType"
ItemTemplateSelector="{StaticResource PackagingTypeLanguageSelector}" />
However, I'm still looking for a more elegant way to solve this.
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}}" ...