SelectedItem dependency property from IntelliBox does not update from Datatemplate - c#

I'm using IntelliBox for showing autocomplete. The SelectedItem dependency property is implemented the following way:
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(Intellibox),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnSelectedItemChanged)));
This is how a new value is set in the usercontrol (IntelliBox)
private void ChooseCurrentItem() {
this.SetValue(SelectedItemProperty, ResultsList.SelectedItem);
_lastTextValue = UpdateSearchBoxText(true);
OnUserEndedSearchEvent();
if (Items != null) {
Items = null;
}
}
The property in the viewmodel
private Recipient selectedRecipient;
public Recipient SelectedRecipient
{
get
{
return selectedRecipient;
}
set
{
selectedRecipient = value;
OnPropertyChanged("SelectedRecipient");
}
}
If I use the IntelliBox without a datatemplate the setter of SelectedRecipient is called after setting value from dependency property.
<Grid>
<StackPanel>
<controls:Intellibox DataProvider="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext.SearchProviderRecipients}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext.SelectedRecipient}">
</controls:Intellibox>
</StackPanel>
</Grid>
Unfortunately if the IntelliBox is within a datatemplate the value of the dependency property is set, but it does not call the setter in the viewmodel.
xaml:
<Grid>
<StackPanel>
<ListView ItemsSource="{Binding Items}" x:Name="ListViewMain">
<ListView.ItemTemplate>
<DataTemplate>
<controls:Intellibox DataProvider="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext.SearchProviderRecipients}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext.SelectedRecipient}">
</controls:Intellibox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
What have I have to do that the setter get called?

Related

How to bind dependency property of ICommand of ItemsControl member to parent view model?

I have a custom user control that has a checkbox and a button with backing dependency properties.
I also have a window with an ItemsControl and I bind that control's "ItemsSource" to an ObservableCollection containing said user controls.
My goal is to be able to access the dependency properties in the window's view model, so I can check whether the checkbox of each member of the collection is checked or not and when the user clicks the button - remove the user control containing the button from the collection.
This is my code
User control XAML:
<CheckBox IsChecked="{Binding cbChecked,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}"/>
<Button Content="X"
Command="{Binding RemoveCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}">
(note I am not sure if I need the "UpdateSourceTrigger")
User control codebehind:
public ICommand RemoveCommand
{
get { return (ICommand)GetValue(RemoveCommandProperty); }
set { SetValue(RemoveCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for RemoveCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RemoveCommandProperty =
DependencyProperty.Register("RemoveCommand", typeof(ICommand), typeof(CustomUserControl), new PropertyMetadata(OnAnyPropertyChanged));
public bool cbChecked
{
get { return (bool)GetValue(cbCheckedProperty); }
set { SetValue(cbCheckedProperty, value); }
}
// Using a DependencyProperty as the backing store for cbChecked. This enables animation, styling, binding, etc...
public static readonly DependencyProperty cbCheckedProperty =
DependencyProperty.Register("cbChecked", typeof(bool), typeof(CustomUserControl), new PropertyMetadata(OnAnyPropertyChanged));
static void OnAnyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
=> (obj as CustomUserControl).OnAnyPropertyChanged(args);
void OnAnyPropertyChanged(DependencyPropertyChangedEventArgs args)
=> AnyPropertyChanged?.Invoke(this, args);
Window XAML:
<ScrollViewer VerticalScrollBarVisibility="Visible" Grid.Row="0">
<ItemsControl ItemsSource="{Binding ControlsCollection}" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="5,20,0,0"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</ScrollViewer>
How can I make it so when the user clicks the "X" button the user control that holds it is removed from the collection AND be able to check the checkbox "checked" status from the window's view model?
First, you should not hold an element of view in view model. The item of ObservableCollection should be item's view model rather than UserControl.
Second, you don't need to use a UserControl in such case. A DataTemplate which has the same elements will suffice.
For example, a minimal item's view model would be as follows.
using CommunityToolkit.Mvvm.ComponentModel;
public partial class ItemViewModel : ObservableObject
{
[ObservableProperty]
public bool _checked;
}
Then, window's view model.
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public class MainWindowViewModel : ObservableObject
{
public ObservableCollection<ItemViewModel> ItemsCollection { get; } = new();
public MainWindowViewModel()
{
ItemsCollection.Add(new ItemViewModel());
ItemsCollection.Add(new ItemViewModel());
ItemsCollection.Add(new ItemViewModel());
}
public ICommand RemoveCommand => _removeCommand ??= new RelayCommand<ItemViewModel>(item => Remove(item));
private RelayCommand<ItemViewModel>? _removeCommand;
private void Remove(ItemViewModel? item)
{
if (item is not null)
{
Debug.WriteLine($"Checked:{item.Checked}");
ItemsCollection.Remove(item);
}
}
}
The window's view. The elements of DataTemplate of ItemsControl.ItemTemplate are the same as your UserControl but bindings are modified.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow"
Width="600" Height="300">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<ScrollViewer VerticalScrollBarVisibility="Visible">
<ItemsControl ItemsSource="{Binding ItemsCollection}"
HorizontalAlignment="Stretch" VerticalAlignment="Top">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="5,20,0,0"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding Checked, Mode=OneWayToSource}"/>
<Button Content="X"
Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Window>

WPF CheckBox Command inside a Listview DataTemplate

My CheckBox Command does not work. I want to pass the SelectedItem of the ListView to the Command when the SelectedItem is checked or unchecked, but the Command does not execute at all. I also suspect my CommandParameter is not configured correctly?
I am pretty sure the problem is because the CheckBox is within a ListView DataTemplate.
Can someone show me how to set this up? I tried to follow examples I found, but nothing seems to work. thanks.
XAML
<ListView x:Name="lvReferralSource" ItemsSource="{Binding ReferralSourceTypeObsCollection}" Style="{StaticResource TypeListViewStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<CheckBox x:Name="ckbReferralIsChecked" Content="{Binding Value}" IsChecked="{Binding Active}" Style="{StaticResource CheckBoxStyleBase2}"
Command="{Binding CheckBoxIsChecked}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=SelectedItem}">
</CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CODE
private ICommand _CheckBoxIsChecked;
public ICommand CheckBoxIsChecked
{
get
{
if (_CheckBoxIsChecked == null)
{
_CheckBoxIsChecked = new RelayCommand<object>(ExecuteCheckBoxIsChecked, CanExecuteCheckBoxIsChecked);
}
return _CheckBoxIsChecked;
}
}
public bool CanExecuteCheckBoxIsChecked(object parameter)
{
return true;
}
public void ExecuteCheckBoxIsChecked(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
if (parameter != null)
{
//Do Stuff...
}
Mouse.OverrideCursor = Cursors.Hand;
}
Your command should get executed provided that the CheckBoxIsChecked property belongs to the data object where the Value and Active properties are defined.
If it belongs to the view model, you could bind to it using a RelativeSource:
<CheckBox x:Name="ckbReferralIsChecked" Content="{Binding Value}" IsChecked="{Binding Active}"
Style="{StaticResource CheckBoxStyleBase2}"
Command="{Binding DataContext.CheckBoxIsChecked, RelativeSource={RelativeSource AncestorType=ListView}}"
CommandParameter="{Binding}">

Binding property in ContextMenu from Parent's Parent's DataContext

I wanted to bind a property in a MenuItem within an ItemsControl container. I have a hierarchical model ; Item class has a list of SubItem. The ViewModel itself has a list of Item (so there's two ItemsControl, one being in the ItemTemplate of the other).
I found several other questions on SO asking about that (this one for instance) and I learned that the Visual Tree of a ContextMenu is separated from the rest.
I managed to do it and it works (but it feels kind of hacky) by "transferring" the model's data via the Tag property.
Here's the two model classes:
public class SubItem
{
public int Current { get; set; }
public Subitem(int current)
{
Current = current;
}
}
public class Item
{
public ObservableCollection<SubItem> SubItems { get; set; }
public string Parent { get; set; }
public Item(string Parent)
{
Parent = Parent;
SubItems = new ObservableCollection<SubItem>();
}
}
Here's the view model:
public class ViewModel
{
public ObservableCollection<Item> Items { get; set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
FillData();
}
private void FillData()
{
//...
}
}
And here's the ItemsControl at the root of the page (the page's DataContext is an instance of the ViewModel class):
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding SubItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Current}"
Tag="{Binding DataContext.Parent, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}, Mode=FindAncestor}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My question is: is this the right way to do it? I tried many other ways to avoid binding the property to the Tag but couldn't make it work.
The ugly part is specifically:
Tag="{Binding DataContext.Parent, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}"
Followed by:
Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}, Mode=FindAncestor}}"
I think it's kind of ugly and I'm sure there's a better way to do that. The solution must work with .NET 4.0.
You need to add a tag to the menu's container and bind to it using placement target.
View this example:
<StackPanel x:Key="ConfigurationListItem" x:Shared="False" Tag="{Binding ElementName=UserControl}">
<StackPanel Orientation="Horizontal">
<Button>
<Button.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ElementName=UserControl, Path=LaunchCommand}" CommandParameter="{Binding}" />
<MouseBinding Gesture="LeftClick" Command="{Binding ElementName=UserControl, Path=SelectCommand}" CommandParameter="{Binding}" />
</Button.InputBindings>
</StackPanel>
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" Tag="{Binding}">
<MenuItem Header="Sync Environment Dependencies"
Command="{Binding Parent.PlacementTarget.Tag.SyncEnvironmentCommand, RelativeSource={RelativeSource Self}}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext}" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>

Delete item from ItemControl

My class is has a ObservableCollection of my viewmodel class and I set the itemsource of the Itemcontrol in xaml as below
<ItemsControl ItemsSource="{Binding ConditionItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Background="#FFD0D7EB">
<StackPanel>
<Button Content="Delete" HorizontalAlignment="Right" Width="180" Margin="0,0,12,10" Command="{Binding DeleteItem}" CommandParameter="{Binding}">
</Button> </StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
For some reason my DeleteItem is never called.
private RelayCommand _DeleteRule;
private void DoDeleteRule(object item)
{
if (item != null)
{
MessageBox.Show("in del");
}
}
public ICommand DeleteItem
{
get
{
if (_DeleteRule == null)
_DeleteRule = new RelayCommand(o => DoDeleteRule(o));
return _DeleteRule;
}
}
Am I doing anything wrong in xaml?
The ItemsControl is bound using {Binding ConditionItems}, so it expects the DeleteItem command to be inside the subitems of that list. I guess this is not the case, the DeleteItem exists on the ViewModel.
You could bind to the DataContext of the Window for example, where you can find the DeleteItem command. Or create a proxy element.
I found it. My xaml should be
<Button Content="Delete" Command="{Binding DataContext.DeleteItem,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}">
</Button>

Binding individual ListView items created in XAML

I have a ListView with ListViewItem's created statically in XAML. I want to bind ListViewItem's properties to different properties in my View Model given as a DataContext for ListView. But my code does not work:
<ListView AlternationCount="2" Name="listView">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Binding.TargetUpdated="TargetUpdated" Text="{Binding Value}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Units}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding About}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
<l:PropertyItem Title="title" Value="{Binding Path=DeviceCode}" Units="units" About="about"/>
<l:PropertyItem Title="title" Value="{Binding Path=VendorName}" Units="units" About="about"/>
<l:PropertyItem Title="title" Value="{Binding Path=Version}" Units="units" About="about"/>
</ListView>
Where NumericItem is:
internal class PropertyItem : DependencyObject
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(String), typeof(PropertyItem), new PropertyMetadata(String.Empty));
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(String), typeof(PropertyItem), new PropertyMetadata(String.Empty));
public static readonly DependencyProperty UnitsProperty =
DependencyProperty.Register("Units", typeof(String), typeof(PropertyItem), new PropertyMetadata(String.Empty));
public static readonly DependencyProperty AboutProperty =
DependencyProperty.Register("About", typeof(String), typeof(PropertyItem), new PropertyMetadata(String.Empty));
public String Value
{
get
{
return (String)this.GetValue(ValueProperty);
}
set
{
this.SetValue(ValueProperty, value);
}
}
public String Title
{
get
{
return (String)this.GetValue(TitleProperty);
}
set
{
this.SetValue(TitleProperty, value);
}
}
public String Units
{
get
{
return (String)this.GetValue(UnitsProperty);
}
set
{
this.SetValue(UnitsProperty, value);
}
}
public String About
{
get
{
return (String)this.GetValue(AboutProperty);
}
set
{
this.SetValue(AboutProperty, value);
}
}
}
I found the solution! My PropertyItem class derived from DependencyObject which in turn does not contain property DataContext. That's why the binding does not work. You need to derive from the one who has DataContext property, for example FrameworkElement and it will work!
Try using these Bindings:
<l:PropertyItem Title="title" Value="{Binding Path=DataContext.DeviceCode,
RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" Units="units"
About="about" />
<l:PropertyItem Title="title" Value="{Binding Path=DataContext.VendorName,
RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" Units="units"
About="about"/>
<l:PropertyItem Title="title" Value="{Binding Path=DataContext.Version,
RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" Units="units"
About="about"/>
Here, we're binding to the DataContext of the parent of type ListView... I believe that's what you were after.

Categories

Resources