RadioButtons in a ListView. SelectedItem not working [duplicate] - c#

This question already has an answer here:
WPF ListView with group of RadioButtons and select default value
(1 answer)
Closed 4 months ago.
Have two properties in my viewmodel, INotifyPropertyChanged is implemented but not shown here. Both Properties are initiated in the constructor and values are set.
public ObservableCollection<Foo> Foos { get; init;}
public Foo SelectedFoo { get => _selectedFoo; set => _selectedFoo = value;}
the foo class:
public class Foo
{
public string NameOfFoo { get; set; }
public bool IsActive { get; set; }
}
in the corresponding View...
<ListView
ItemsSource="{Binding Foos}"
SelectedItem="{Binding SelectedFoo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Background="#FF063852"
BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding NameOfFoo}"
GroupName="GroupSelector"
BorderThickness="0"
Style="{StaticResource RadioButtonStyle}"
IsChecked="{Binding IsActive}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding Path=DataContext.SelectGroupCommand,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</RadioButton>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Margin="5" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
ItemsSource of ListView is working fine.
The radioButton is not checked as expected after creating new instance from viewmodel.
The command for the event "checked" is ok.
The SETTER from the SelectedFoo propery is never called. Why??
What have I to do, to have the propper radiobutton checked in my view after creation?

Found a solution here: https://stackoverflow.com/a/24179089/14800168
Multibinding for IsChecked was the trick!

Related

Struggling to template a WPF MenuItem's Icon with control

Following on from a previous question here, I'm struggling to style a MenuItem's Icon with a control I have that inserts icon images based upon a string dependency property.
Initially I started with:
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
...
<Setter Property="Icon">
<local:StringToIcon IconName="{Binding IconName}" />
</Setter>
</Style>
</ContextMenu.Resources>
</ContextMenu>
This had the predictable effect of only displaying one of the icons in the menu, usually the last one, as the instance was shared around.
I then tried the non-shared resource approach:
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<Style TargetType="MenuItem">
...
<Setter Property="Icon" Value="{StaticResource MenuIcon} />
</Style>
</ContextMenu.Resources>
</ContextMenu>
This had no effect. It didn't offer me x:Shared in Intellisense, so I wonder if that's an invalid property here.
Out of desperation, I threw the thing into a template:
<Setter Property="Icon">
<Setter.Value>
<ContentControl>
<ContentControl.Template>
<ControlTemplate>
<local:StringToIcon IconName="{Binding IconName}" />
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</Setter.Value>
</Setter>
Again, no effect. My StringToIcon looks like this at the moment, hard-coded with a single image to check the problem doesn't lie there. (Or does it?)
<UserControl x:Class="RAP.Admin3.Components.StringToIcon"
...
>
<Image DataContext="{Binding ElementName=StringIconControl}" Source="pack://application:,,,/Resources/Icons/lorry.png"/>
</UserControl>
How do I get this darn thing to template and allow multiple uses? It's probably something basic I'm overlooking.
I've looked at various similar questions, and most seem to have success with the non-shared resource method.
Edit: Let me add substantially more code as requested. I've come up with a minimal replication of the problem:
The context menu is part of a TreeView resource.
<UserControl x:Class="MyApp.ItemHierarchy"
...
Name="ItemHierarchyControl">
<Grid>
<TreeView ItemsSource="{Binding ElementName=ItemHierarchyControl, Path=Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:HierarchyItem}" ItemsSource="{Binding Subitems}">
<StackPanel Orientation="Horizontal" Margin="0,1,4,1">
<TextBlock Text="My text" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}" />
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</UserControl>
This is backed by a dependency property for the items.
public ObservableCollection<HierarchyItem> Items
{
get { return (ObservableCollection<HierarchyItem>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<HierarchyItem>), typeof(ItemHierarchy), new PropertyMetadata(new ObservableCollection<HierarchyItem>()));
StringToIcon is also backed by a string dependency property for the icon name, which is summarily ignored because of the hard-coded image at the moment.
HierarchyItems are simple for the example:
public ObservableCollection<HierarchyItem> Subitems { get; set; }
public ObservableCollection<BindableMenuItem> MenuItems { get; set; }
Just to get this proof working, I attached the ItemHierarchy to some properties of the main window:
public ObservableCollection<BindableMenuItem> MenuItems { get; set; }
public ObservableCollection<HierarchyItem> IHItems { get; set; }
public MainWindow()
{
MenuItems = new ObservableCollection<BindableMenuItem>();
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
IHItems = new ObservableCollection<HierarchyItem>();
IHItems.Add(new HierarchyItem() { MenuItems = this.MenuItems });
InitializeComponent();
}
Edit 2: Here's BindableMenuItem also:
public class BindableMenuItem
{
public BindableMenuItem(string name, ICommand command)
{
this.Name = name;
this.Command = command;
}
public string Name { get; set; }
public ICommand Command { get; set; }
public string IconName { get; set; }
public ObservableCollection<BindableMenuItem> Children { get; set; }
}
Try to move the StringToIcon to <TreeView.Resources>:
<TreeView ItemsSource="{Binding ElementName=ItemHierarchyControl, Path=Items}">
<TreeView.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<HierarchicalDataTemplate DataType="{x:Type local:HierarchyItem}" ItemsSource="{Binding Subitems}">
<StackPanel Orientation="Horizontal" Margin="0,1,4,1">
<TextBlock Text="My text" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}" />
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>

Access the Window's DataContext from a DataTemplate in a DataGrid

I use a DataGrid in my WPF application. It does feature RowDetails for selected Rows. Therefor I set the RowDetailsTemplate. Inside this DataTemplate i want to access my Window's DataContext. For Example I have a lable inside my RowDetailsTemplate and I want to bind it's content-property to a property of my viewModel which is in the DataContext of the window. How do I achieve this.
Thank you for your help!
Look at this usage of RelativeSource based binding, like {Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}. Here is example:
Xaml:
<DataGrid ItemsSource="{Binding Strings}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="String" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type soDataGridHeplAttempt:ClicableItemsModel}">
<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding ClickableItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Width="70" Content="{Binding }" Style="{StaticResource ButtonInCellStyle}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.Command}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Content}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
View model:
private ICommand _command;
public ObservableCollection<ClicableItemsModel> Strings { get; set; }
public ICommand Command
{
get { return _command ?? (_command = new RelayCommand<object>(MethodOnCommmand)); }
}
private void MethodOnCommmand(object obj)
{
}
Model pu this inside the the model class:
public ObservableCollection<String> ClickableItems { get; set; }
regards,

How to display a clickable list of things to datagrid cell?

I have a datagrid, in one of the columns I want to display a string - in each cell in the column a different string(that I get from the user) that looks like this: "data1, data2, data3, ..." and make each datai(data1,data2,...) clickable.
how can I achieve that?
Here is edited version:
Xaml (put it inside the <DataGridTemplateColumn.CellTemplate):
<DataTemplate DataType="{x:Type soDataGridHeplAttempt:ClicableItemsModel}">
<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding ClickableItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Width="70" Content="{Binding }" Style="{StaticResource ButtonInCellStyle}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.Command}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Content}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
Xaml resources use this if you want to edit the button content:
<Style x:Key="ButtonInCellStyle" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Background="{x:Null}" MaxWidth="40" BorderBrush="{x:Null}" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type Button}}, Path=Content, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
HorizontalAlignment="Center" VerticalAlignment="Center" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}">
</TextBox>
</DataTemplate>
</Setter.Value>
</Setter>
Viewmodel:
private ICommand _command;
public ObservableCollection<ClicableItemsModel> Strings { get; set; }
public ICommand Command
{
get { return _command ?? (_command = new RelayCommand<object>(MethodOnCommmand)); }
}
private void MethodOnCommmand(object obj)
{
}
Clickable Items model code (create the ClicableItemsModel class an put it there):
public ObservableCollection<String> ClickableItems { get; set; }
when running:
regards,

Clicking a button in a listviewitem that is not selected and getting selected item

I have a question about a listview setup like the example below. When I click the button below in the expander header I want that item to be selected as well, but what I'm seeing is while the button command does work, the item selected is still the previous item selected, not the item my button is in. How can I have the Item selected when the button is clicked?
I tried setting up a ControlTemplate like this, but it did not work.
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ListView ItemsSource="{Binding MyItemSource,
Mode=TwoWay}"
SelectedItem="{Binding MySelectedItem,
Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}, Path=IsSelected}">
<Expander.Header>
<Button Command={Binding MyCommand}>Click Me</Button>
</Expander.Header>
<!-- content here -->
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I would suggest defining a command SelectItem in the main ViewModel which takes the item which is to be selected as a parameter. The execution method of this command can then set the MySelectedItem property, set a property IsSelected on the item ViewModel to true and invoke all further actions on the item itself (i.e. what is now executed by MyCommand). With the selection logic in the ViewModel and a clean binding you don't even need to use ListView at all but can stick to a plain ItemsControl:
The XAML then looks like this:
<ItemsControl ItemsSource="{Binding MyItemSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="{Binding IsSelected}">
<Expander.Header>
<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}, Path=DataContext.SelectItem}"
CommandParameter="{Binding"}>Click Me</Button>
</Expander.Header>
<!-- content here -->
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The MainViewModel would look something like this:
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<ItemViewModel> MyItemsSource { get; private set; }
public ItemViewModel SelectedItem { get... set... }
public ICommand SelectItem { get; private set; }
ctor()...
private void ExecuteSelectItem(ItemViewModel item)
{
SelectedItem = item;
foreach (var i in MyItemsSource) i.IsSelected = false;
item.IsSelected = true;
item.DoSomething();
}
}
I have always found it way easier to use ItemsControl an implement the few lines of selection logic myself, instead of dealing with the messy binding of the selection of a ListView. In my opinion it is a quite intuitive to implement custom selection behavior (multiple items, allowing only certain combinations, etc.). You can use the IsSelected property easily to apply a custom styling of selected items.
You can try something like this in your view model, add if statement in setter:
private object _mySelectedItem;
public object MySelectedItem
{
get { return _mySelectedItem; }
set
{
if (_mySelectedItem != value && value != null)
{
_mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
}

Why does my context menu look different when generated dynamically

I am trying to dynamically bind context menus from my base view model. I've managed to functionally do what i'd like to do, but I'm stuck with how my dynamic context menu looks compared to my static one. There seems to be an extra level of menu items. My View Model looks like this.
public ObservableCollection<ContextMenuItem> ContextMenuItems { get; protected set; }
protected Constructor
{
ContextMenuItems = new ObservableCollection<ContextMenuItem>()
{
new ContextMenuItem() {Caption = "Add/Replace Supporting Data", Command = AddReplaceSupportingCommand},
new ContextMenuItem() {Caption = "Display SupportingData", Command = DisplaySupportingDataCommand}
};
}
the xaml for the 1st context menu us generated like this.
<StackPanel Orientation="Vertical" Margin="0,20">
<StackPanel.ContextMenu >
<ContextMenu ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Command="{Binding Command}" Header="{Binding Caption}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</StackPanel.ContextMenu>
...
</StackPanel>
The xaml for the second context menu looks like this
<StackPanel >
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding AddReplaceSupportingCommand}" Header="Add or Replace Supporting Data"/>
<MenuItem Command="{Binding DisplaySupportingDataCommand}" Header="Display Supporting Data"/>
</ContextMenu>
</StackPanel.ContextMenu>
...
</StackPanel>
(for completeness, here's the ContextMenuItem class)
public class ContextMenuItem : ReactiveObject
{
private string _Caption;
public string Caption
{
get { return _Caption; }
set { this.RaiseAndSetIfChanged(ref _Caption, value); }
}
public ReactiveCommand Command { get; set; }
}
It looks different because ContextMenu, MenuBase to be specific, uses MenuItem as ItemContainerStyle so you're effectively wrapping MenuItem within MenuItem. Try this instead:
<StackPanel Orientation="Vertical" Margin="0,20">
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Caption}"/>
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</StackPanel.ContextMenu>
<!-- ... -->
</StackPanel>
you can use ItemTemplate if you want but use something like TextBox instead
<StackPanel Orientation="Vertical" Margin="0,20">
<StackPanel.ContextMenu >
<ContextMenu ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.ItemContainerStyle>
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</StackPanel.ContextMenu>
<!-- ... -->
</StackPanel>

Categories