Binding issue in ItemsControl within a UserControl in WPF - c#

I created a simple example which replicates the problem I am having with a much bigger usercontrol - application interaction. The controls have been changed to simplify, but reflect the exact problem.
I have a user control (CheckBoxTable) which creates a grid of checkboxes based upon the property CheckBoxData:
<UserControl
x:Class="WPFNotWorkingTest.CheckBoxTable"
x:Name="CheckBoxTableName">
<ItemsControl DataContext="{Binding ElementName=CheckBoxTableName}" ItemsSource="{Binding CheckBoxData}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding GridRow}" />
<Setter Property="Grid.Column" Value="{Binding GridColumn}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid ShowGridLines="True" Style="{Binding Path=Style}" Loaded="OnGrid_Loaded"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsCheckBoxChecked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
The ItemSource CheckBoxData is an ObservableCollection within the CheckBoxTable user control.
public ObservableCollection<CheckBoxTableData> CheckBoxData
{
get { return (ObservableCollection<CheckBoxTableData>)GetValue(CheckBoxDataProperty); }
set { SetValue(CheckBoxDataProperty, value); }
}
// Using a DependencyProperty as the backing store for CheckBoxData. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CheckBoxDataProperty =
DependencyProperty.Register("CheckBoxData", typeof(ObservableCollection<CheckBoxTableData>), typeof(CheckBoxTable), new PropertyMetadata(null, new PropertyChangedCallback(CheckBoxTable.OnCheckBoxData_Changed)));
private static void OnCheckBoxData_Changed(DependencyObject dObject, DependencyPropertyChangedEventArgs e)
{
CheckBoxTable table = (CheckBoxTable)dObject;
ObservableCollection<CheckBoxTableData> objOldValue = (ObservableCollection<CheckBoxTableData>)e.OldValue;
if (objOldValue != null)
{
objOldValue.CollectionChanged -= table.OnTableData_CollectionChanged;
}
ObservableCollection<CheckBoxTableData> objNewValue = (ObservableCollection<CheckBoxTableData>)e.NewValue;
if (objNewValue != null)
{
objNewValue.CollectionChanged += table.OnTableData_CollectionChanged;
}
}
CheckBoxTableData class
public class CheckBoxTableData : DependencyObject
{
public bool? IsCheckBoxChecked
{
get { return (bool?)GetValue(IsCheckBoxCheckedProperty); }
set { SetValue(IsCheckBoxCheckedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsCheckBoxChecked. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckBoxCheckedProperty =
DependencyProperty.Register("IsCheckBoxChecked", typeof(bool?), typeof(CheckBoxTableData), new PropertyMetadata(true));
public int GridRow
{
get { return (int)GetValue(GridRowProperty); }
set { SetValue(GridRowProperty, value); }
}
// Using a DependencyProperty as the backing store for GridRow. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GridRowProperty =
DependencyProperty.Register("GridRow", typeof(int), typeof(CheckBoxTableData), new PropertyMetadata(0));
public int GridColumn
{
get { return (int)GetValue(GridColumnProperty); }
set { SetValue(GridColumnProperty, value); }
}
// Using a DependencyProperty as the backing store for GridColumn. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GridColumnProperty =
DependencyProperty.Register("GridColumn", typeof(int), typeof(CheckBoxTableData), new PropertyMetadata(0));
}
}
Usage in a window:
<Window x:Class="WPFNotWorkingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFNotWorkingTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Click Me" Click="Button_Click" Margin="5"/>
<local:CheckBoxTable Grid.Row="1" CheckBoxPerRow="10">
<local:CheckBoxTable.CheckBoxData>
<local:CheckBoxTableData IsCheckBoxChecked="True"/>
<local:CheckBoxTableData IsCheckBoxChecked="False"/>
<local:CheckBoxTableData IsCheckBoxChecked="{Binding CheckMyBinding}"/>
</local:CheckBoxTable.CheckBoxData>
</local:CheckBoxTable>
</Grid>
</Window>
MainWindow.cs
public partial class MainWindow : Window
{
public bool? CheckMyBinding
{
get { return (bool?)GetValue(CheckMyBindingProperty); }
set { SetValue(CheckMyBindingProperty, value); }
}
// Using a DependencyProperty as the backing store for CheckMyBinding. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CheckMyBindingProperty =
DependencyProperty.Register("CheckMyBinding", typeof(bool?), typeof(MainWindow), new PropertyMetadata(false));
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (CheckMyBinding == true)
{
CheckMyBinding = false;
}
else
{
CheckMyBinding = true;
}
}
}
Clicking the button and executing the handler does NOT toggle the IsChecked for the CheckBox. The problem seems to be the binding in the Window and not the User Control and I have wracked my brain trying to figure out why.

This happens because your CheckBoxTableData has no way of knowing about your MainWindow. The CheckBoxes generated by the ItemsControl in CheckBoxTable each have an instance of CheckBoxData as their DataSource. The CheckBoxTableData however don't have a DataContext of their own and you can't use a RelativeSource because they are not part of the visual or logical tree of your application.
The XAML designer may make it look like it works (showing autocomplete and all), but that's just because it doesn't know how exactly your control works.
You could work around this by setting this Binding manually in your code like this:
CheckBoxTableData tableData = new CheckBoxTableData();
BindingOperations.SetBinding(tableData, CheckBoxTableData.IsCheckBoxCheckedProperty,
new Binding("CheckMyBinding") {Source = this}); // this = MainWindow
checkBoxTable.CheckBoxData = new ObservableCollection<CheckBoxTableData>();
checkBoxTable.CheckBoxData.Add(tableData);
However it would be a lot cleaner to take care of this in your ViewModel instead of adding that property to the MainWindow.

Related

How do I use command and binding together in WPF?

I've been practicing MVVM pattern and come across the problem which I don't know how to solve. The problem is pretty simple and I hope the solution as well. The point is that I'm trying to use a command and binding for an element, when I'm setting up it's style, but I can't do it at the same time.
I have the following style for ListBoxItem:
<Style x:Key="OptionDieStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Width="Auto"
BorderThickness="1.5"
CornerRadius="10"
Height="30"
Background="Transparent"
Margin="5">
<TextBlock Margin="5"
Text="{Binding}"
Foreground="White"
VerticalAlignment="Center"/>
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="#Omitted"
</Border.InputBindings>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This ListBox is filled with strings which are displayed in particular way because of the style.
That means that when I want to handle user's click on that element, using command, I need to set DataContext, which contains ViewModel, where command is located, for this item, but if I do it no content will be displayed in ListBox Items. Certainly, I could set event for this Border like "MouseDown" but it would be the wrong way to use MVVM.
If you have some thoughts how to solve this using commands please share them.
To make these scenarios easier, I've derived a class from CommandBindin. In which he added the ability to bind to ViewModel commands. You can set the binding to both Execute and PreviewExecute.
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
namespace CommonCore.AttachedProperties
{
public class CommandBindingHelper : CommandBinding
{
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(CommandBindingHelper),
new PropertyMetadata(null));
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty PreviewCommandProperty =
DependencyProperty.RegisterAttached(
"PreviewCommand",
typeof(ICommand),
typeof(CommandBindingHelper),
new PropertyMetadata(null));
public BindingBase Binding { get; set; }
public BindingBase PreviewBinding { get; set; }
public CommandBindingHelper()
{
Executed += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, CommandProperty, Binding);
CanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, CommandProperty, Binding);
PreviewExecuted += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
PreviewCanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
}
private static void PrivateExecuted(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
{
ICommand command = GetCommand(sender, commandProp, commandBinding);
if (command is not null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
private static bool PrivateCanExecute(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
{
ICommand command = GetCommand(sender, commandProp, commandBinding);
return command?.CanExecute(parameter) ?? true;
}
private static UIElement CheckSender(object sender)
{
if (sender is not UIElement element)
throw new NotImplementedException("Implemented only for UIElement.");
return element;
}
private static ICommand GetCommand(UIElement sender, DependencyProperty commandProp, BindingBase commandBinding)
{
BindingBase binding = BindingOperations.GetBindingBase(sender, commandProp);
if (binding != commandBinding)
{
if (commandBinding is null)
{
BindingOperations.ClearBinding(sender, commandProp);
}
else
{
BindingOperations.SetBinding(sender, commandProp, commandBinding);
}
}
return (ICommand)sender.GetValue(CommandProperty);
}
}
}
An example of its use:
using Simplified; // This is the space of my ViewModelBase implementation
using System.Collections.ObjectModel;
namespace Core2023.SO.ASTERY.CommandInListItem
{
public class ListItemsViewModel : ViewModelBase
{
public ObservableCollection<string> Items { get; } = new("first second third fourth fifth".Split());
public RelayCommand RemoveCommand => GetCommand<string>(item => Items.Remove(item));
}
}
<Window x:Class="Core2023.SO.ASTERY.CommandInListItem.ListItemsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Core2023.SO.ASTERY.CommandInListItem"
xmlns:ap="clr-namespace:CommonCore.AttachedProperties;assembly=CommonCore"
mc:Ignorable="d"
Title="ListItemsWindow" Height="450" Width="800"
FontSize="20">
<Window.DataContext>
<local:ListItemsViewModel/>
</Window.DataContext>
<Window.CommandBindings>
<ap:CommandBindingHelper Command="Delete" Binding="{Binding RemoveCommand}"/>
</Window.CommandBindings>
<Grid>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1" Margin="5">
<TextBlock Text="{Binding}"/>
<Button Content="Remove"
Command="Delete"
CommandParameter="{Binding}"/>
</UniformGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
What do i do if I want to set different name?
The easiest way is to create a command in Window or (better) App resources.
<Application.Resources>
<RoutedUICommand x:Key="commands.Remove" Text="Delete Item" />
</Application.Resources>
<Button Content="Remove"
Command="{StaticResource commands.Remove}"
CommandParameter="{Binding}"/>
Create a static property containing the command. But it should be created at the View level, not the ViewModel.
public static class MyCommands
{
public static RoutedUICommand Remove { get; }
= new RoutedUICommand("Delete Item", "Remove", typeof(MyCommands));
public static RoutedUICommand Add { get; }
= new RoutedUICommand("Add Item", "Add", typeof(MyCommands));
}
<Button Content="Remove"
Command="{x:Static local:MyCommands.Remove}"
CommandParameter="{Binding}"/>
Adding a markup extension to the previous version to make it easier to use in XAML.
public class MyCommandsExtension : MarkupExtension
{
public string? CommandName { get; set; }
public MyCommandsExtension() { }
public MyCommandsExtension(string commandName) => CommandName = commandName;
public override object ProvideValue(IServiceProvider serviceProvider)
=> CommandName switch
{
nameof(MyCommands.Remove) => MyCommands.Remove,
nameof(MyCommands.Add) => MyCommands.Add,
_ => throw new NotImplementedException()
};
}
<Button Content="Remove"
Command="{local:MyCommands Remove}"
CommandParameter="{Binding}"/>
The approach above is working fine, but only if we're going to use commands with default ApplicationCommands' names and won't give them individual names. I was racking my brains and eventually found the proper approach.
All I had to do is just make my command static in ViewModel and change definition for my command in XAML like this:
Command="{x:Static viewModels:MyViewModel.MyCommand}

Custom ComboBox throw exception when change ItemsSource

I have a UserControl embedding a ComboBox and some validation-related stuff.
It works until I try to update the ItemsSource (this happens whenI change the first ComboBox "ChoixResidence"): I get "System.NullReferenceException: 'Object reference not set to an instance of an object.'"
[EDIT] To be noted that it works with regular ComboBox instead of ValComboBox.
Exception when updating ItemsSource screen capture
I tryed to set ItemsSource to null before, with no success.
UserControl:
XAML:
<UserControl
x:Class="GestionGarages.Controls.ValComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GestionGarages.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<StackPanel>
<TextBlock Margin="0,0,0,5" Text="{x:Bind Title, Mode=OneWay}"/>
<ComboBox x:Name="List"
Width="{x:Bind Width, Mode=OneWay}"
DisplayMemberPath="{x:Bind MemberPath, Mode=OneWay}"
SelectedValuePath="{x:Bind ValuePath, Mode=OneWay}"
SelectedValue="{x:Bind SelectedValue, Mode=TwoWay}">
</ComboBox>
<TextBlock
x:Name="DisplayErrorMessage"
Text="{x:Bind ErrorMessage, Mode=OneWay}"
FontSize="10"
Foreground="Red"
Height="14"/>
</StackPanel>
</Grid>
</UserControl>
Code behind:
using System;
using System.Collections;
using System.ComponentModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace GestionGarages.Controls
{
public sealed partial class ValComboBox : UserControl
{
public ValComboBox()
{
this.InitializeComponent();
Loaded += ValComboBox_Loaded;
}
private void ValComboBox_Loaded(object sender, RoutedEventArgs e)
{
Binding selectedIdemBinding = new Binding
{
Source = this,
Mode = BindingMode.TwoWay,
ElementName = "SelectedItem"
};
List.SetBinding(ComboBox.SelectedItemProperty, selectedIdemBinding);
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set {
SetValue(ItemsSourceProperty, value);
List.ItemsSource = value;
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ValComboBox), new PropertyMetadata(null));
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); List.SelectedItem = SelectedItem; }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(ValComboBox), new PropertyMetadata(null));
public string ValuePath
{
get { return (string)GetValue(ValuePathProperty); }
set { SetValue(ValuePathProperty, value); }
}
// Using a DependencyProperty as the backing store for ValuePath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValuePathProperty =
DependencyProperty.Register("ValuePath", typeof(string), typeof(ValComboBox), new PropertyMetadata(""));
public string MemberPath
{
get { return (string)GetValue(MemberPathProperty); }
set { SetValue(MemberPathProperty, value); }
}
// Using a DependencyProperty as the backing store for MemberPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MemberPathProperty =
DependencyProperty.Register("MemberPath", typeof(string), typeof(ValComboBox), new PropertyMetadata(""));
public int SelectedValue
{
get { return (int)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(int), typeof(ValComboBox), new PropertyMetadata(0));
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(ValComboBox), new PropertyMetadata(""));
public string ErrorMessage
{
get { return (string)GetValue(ErrorMessageProperty); }
set { SetValue(ErrorMessageProperty, value); }
}
// Using a DependencyProperty as the backing store for Field. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ErrorMessageProperty =
DependencyProperty.Register("ErrorMessage", typeof(string), typeof(ValTextBox), new PropertyMetadata(""));
public event SelectionChangedEventHandler SelectionChanged
{
add { List.SelectionChanged += value; }
remove { List.SelectionChanged -= value; }
}
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value,
[System.Runtime.CompilerServices.CallerMemberName] String p = null)
{
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
This UserControl is used in this code:
XAML:
<ContentDialog
x:Class="GestionGarages.Controls.AddModifContrat"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GestionGarages.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:GestionGarages.Core.Models"
mc:Ignorable="d"
d:DesignHeight="1000"
d:DesignWidth="800"
Height="Auto"
Width="Auto"
Title="Ajout / Modification Contrat"
PrimaryButtonText="OK"
SecondaryButtonText="Annuler"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
SecondaryButtonClick="ContentDialog_SecondaryButtonClick"
Background="{ThemeResource ContentDialogBackgroundThemeBrush}"
Foreground="{ThemeResource ContentDialogContentForegroundBrush}"
Margin="{ThemeResource ContentDialogBorderWidth}">
<Grid x:Name="AddModifContratGrid">
<StackPanel Orientation="Vertical">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:ValComboBox
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
x:Name="ChoixResidence"
Title="Résidence"
Width="400"
MemberPath="Nom"
ValuePath="ResidenceId"
SelectionChanged="ChoixResidence_SelectionChanged"
ErrorMessage=""/>
<local:ValComboBox
Grid.Row="1" Grid.Column="0"
x:Name="ChoixGarage"
Title="Garage"
Width="200"
MemberPath="Numero"
ValuePath="GarageId"
SelectionChanged="ChoixGarage_SelectionChanged"
ErrorMessage=""/>
</Grid>
</StackPanel>
</Grid>
</ContentDialog>
Code behind (part of):
private void ChoixResidence_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Residence residence = residences.Where(g => g.ResidenceId == (int)ChoixResidence.SelectedValue).FirstOrDefault();
garages = residence.Garages.ToList();
ChoixGarage.ItemsSource = garages;
ChoixGarage.SelectedItem = garages.FirstOrDefault();
}
private void ChoixGarage_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
garage = garages.Where(g => g.GarageId == (int)ChoixGarage.SelectedValue).FirstOrDefault();
}

Validation not working for custom usercontrol combobox

I am creating a Usercontrol which will contain a dropdown and on opening of dropdown, I want to have an Add button. Everything is working fine but validation is not working for Usercontrol controls.
Here is my xaml code:
<UserControl x:Class="Splendid.Inventory.Presentation.Controls.CustomComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Splendid.Inventory.Presentation.Controls"
xmlns:conv="clr-namespace:Splendid.Inventory.Presentation.Converters"
mc:Ignorable="d" x:Name="ucCombo" Validation.ErrorTemplate="{x:Null}"
d:DesignHeight="30" d:DesignWidth="100">
<UserControl.Resources>
<KeyBinding x:Key="addNewBinding" Key="N" Modifiers="Shift" Command="{Binding AddNewCommand}"></KeyBinding>
<conv:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></conv:BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<ComboBox x:Name="hdnCombo" MaxDropDownHeight="200" Visibility="Visible" Style="{StaticResource DialogCombobox}"
DisplayMemberPath="{Binding DisplayMemberPath, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
SelectedValuePath="{Binding SelectedValuePath, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
SelectedValue="{Binding SelectedValue,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
ItemsSource="{Binding ItemSource, UpdateSourceTrigger=PropertyChanged}"
GotFocus="hdnCombo_GotFocus" LostFocus="hdnCombo_LostFocus">
</ComboBox>
<Canvas Visibility="{Binding IsDropDownOpen, ElementName=hdnCombo, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid Canvas.Top="200">
<Button HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="btnTesst"
MouseEnter="btnTesst_MouseEnter">Add New (Shift + N)</Button>
</Grid>
</Canvas>
</Grid>
</UserControl>
I tried usercontrol with string property binding and textbox and I am able to get errors.
Here is my xaml.cs code:
public partial class CustomComboBox : UserControl, INotifyPropertyChanged, IDataErrorInfo
{
public ICommand AddNewCommand { get; set; }
public CustomComboBox()
{
InitializeComponent();
//this.DataContext = this;
LayoutRoot.DataContext = this;
AddNewCommand = new DelegateCommand(OnAddNewCommand);
}
private void OnAddNewCommand()
{
MessageBox.Show("Add New form");
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public IEnumerable ItemSource
{
get { return (IEnumerable)GetValue(ItemSourceProperty); }
set
{
SetValue(ItemSourceProperty, value);
}
}
// Using a DependencyProperty as the backing store for ListItemSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemSourceProperty =
DependencyProperty.Register("ItemSource", typeof(IEnumerable), typeof(CustomComboBox));
public string AddItemControl
{
get { return (string)GetValue(AddItemControlProperty); }
set { SetValue(AddItemControlProperty, value); }
}
// Using a DependencyProperty as the backing store for AddItemControl. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AddItemControlProperty =
DependencyProperty.Register("AddItemControl", typeof(string), typeof(CustomComboBox), new PropertyMetadata(string.Empty));
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberNameProperty); }
set { SetValue(DisplayMemberNameProperty, value); }
}
// Using a DependencyProperty as the backing store for DisplayMemberPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayMemberNameProperty =
DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(CustomComboBox), new PropertyMetadata(string.Empty));
public string SelectedValuePath
{
get { return (string)GetValue(SelectedValueNameProperty); }
set { SetValue(SelectedValueNameProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValuePath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueNameProperty =
DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(CustomComboBox), new PropertyMetadata(string.Empty));
public SelectListModel SelectedItem
{
get { return (SelectListModel)GetValue(SelectedItemProperty); }
set
{
SetValue(SelectedItemProperty, value);
}
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(SelectListModel), typeof(CustomComboBox));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(CustomComboBox));
public int ItemUnitId
{
get { return (int)GetValue(ItemUnitIdProperty); }
set { SetValue(ItemUnitIdProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemUnitIdProperty =
DependencyProperty.Register("ItemUnitId", typeof(string), typeof(CustomComboBox), new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
public string Error
{
get
{
return "Item type is required";
//throw new NotImplementedException();
}
}
public string this[string columnName]
{
get
{
// use a specific validation or ask for UserControl Validation Error
return Validation.GetHasError(this) ? Convert.ToString(Validation.GetErrors(this).FirstOrDefault().ErrorContent) : null;
}
}
private void btnTesst_MouseEnter(object sender, MouseEventArgs e)
{
OnAddNewCommand();
}
bool isFocused = false;
private void hdnCombo_GotFocus(object sender, RoutedEventArgs e)
{
isFocused = true;
}
private void hdnCombo_LostFocus(object sender, RoutedEventArgs e)
{
isFocused = false;
}
}
Is there any missing piece?
Resolved the issue by adding validation template to User control. Here are the changes made to UserControl:
<UserControl x:Class="Splendid.Inventory.Presentation.Controls.CustomComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Splendid.Inventory.Presentation.Controls"
xmlns:conv="clr-namespace:Splendid.Inventory.Presentation.Converters"
mc:Ignorable="d" x:Name="ucCombo"
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
d:DesignHeight="30" d:DesignWidth="100">
<UserControl.Resources>
<KeyBinding x:Key="addNewBinding" Key="N" Modifiers="Shift" Command="{Binding AddNewCommand}"></KeyBinding>
<conv:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></conv:BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<ComboBox x:Name="hdnCombo" MaxDropDownHeight="200" Visibility="Visible" Style="{StaticResource DialogCombobox}"
DisplayMemberPath="{Binding DisplayMemberPath, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
SelectedValuePath="{Binding SelectedValuePath, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
SelectedValue="{Binding SelectedValue,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
ItemsSource="{Binding ItemSource, UpdateSourceTrigger=PropertyChanged}"
GotFocus="hdnCombo_GotFocus" LostFocus="hdnCombo_LostFocus">
</ComboBox>
<Canvas Visibility="{Binding IsDropDownOpen, ElementName=hdnCombo, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid Canvas.Top="200">
<Button HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="btnTesst"
MouseEnter="btnTesst_MouseEnter">Add New (Shift + N)</Button>
</Grid>
</Canvas>
</Grid>
</UserControl>

Cannot bind to a collection from ItemsControl template

I'm trying to bind a user control property "MyUserControl.Names" to a collection property "Names" of the main window. It doesn't work if I do it in ItemsControl template, but it works if I move the control definition out of the ItemsControl template. Here is the xaml:
<Window x:Class="TestItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestItemsControl"
Height="200" Width="200"
Name="MainControl">
<StackPanel>
<ItemsControl ItemsSource="{Binding Groups, ElementName=MainControl}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- This doesn't work -->
<StackPanel Background="WhiteSmoke" Height="40" Width="100" Margin="5" HorizontalAlignment="Left">
<TextBlock Text="{Binding .}"/>
<local:MyUserControl Names="{Binding Names, ElementName=MainControl}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- This works -->
<StackPanel Background="WhiteSmoke" Height="40" Width="100" Margin="5" HorizontalAlignment="Left">
<TextBlock Text="Group3"/>
<local:MyUserControl Names="{Binding Names, ElementName=MainControl}"/>
</StackPanel>
</StackPanel>
</Window>
MainWindow.xaml.cs contains two dependency properties:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SetValue(GroupsProperty, new ObservableCollection<string>());
SetValue(NamesProperty, new ObservableCollection<string>());
Groups.Add("Group1");
Groups.Add("Group2");
Names.Add("Name1");
Names.Add("Name2");
}
public static readonly DependencyProperty GroupsProperty =
DependencyProperty.Register("Groups", typeof(ObservableCollection<string>), typeof(MainWindow),
new PropertyMetadata(null));
public ObservableCollection<string> Groups
{
get { return (ObservableCollection<string>)GetValue(GroupsProperty); }
set { SetValue(GroupsProperty, value); }
}
public static readonly DependencyProperty NamesProperty =
DependencyProperty.Register("Names", typeof(ObservableCollection<string>), typeof(MainWindow),
new PropertyMetadata(null));
public ObservableCollection<string> Names
{
get { return (ObservableCollection<string>)GetValue(NamesProperty); }
set { SetValue(NamesProperty, value); }
}
}
Here is the result:
The first two rectangles are what ItemsControl generates. The third one is what I have manually added right after the ItemsControl. As you can see, even though the code is exactly the same in both cases, the first two rectangles don't have names, but the third one has. Is there any reason why wouldn't it work with ItemsControl?
Edit:
Here is the code of the MyUserControl.xaml.cs:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
SetValue(NamesProperty, new ObservableCollection<string>());
}
public static readonly DependencyProperty NamesProperty = DependencyProperty.Register(
"Names", typeof(ObservableCollection<string>), typeof(MyUserControl),
new PropertyMetadata(null, NamesPropertyChanged));
public ObservableCollection<string> Names
{
get { return (ObservableCollection<string>)GetValue(NamesProperty); }
set { SetValue(NamesProperty, value); }
}
private static void NamesPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
oldCollection.CollectionChanged -= control.NamesCollectionChanged;
if (newCollection != null)
newCollection.CollectionChanged += control.NamesCollectionChanged;
control.UpdateNames();
}
private void NamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateNames();
}
private void UpdateNames()
{
NamesPanel.Children.Clear();
if (Names == null)
return;
foreach(var name in Names)
{
var textBlock = new TextBlock();
textBlock.Text = name + ", ";
NamesPanel.Children.Add(textBlock);
}
}
}
MyUserControl.xaml:
<UserControl x:Class="TestItemsControl.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestItemsControl"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Name="ParentControl">
<StackPanel Name="NamesPanel" Orientation="Horizontal"/>
</UserControl>
Replace SetValue in the UserControl's constructor by SetCurrentValue. It may even make sense not to assign an initial value at all for the Names property.
public MyUserControl()
{
InitializeComponent();
SetCurrentValue(NamesProperty, new ObservableCollection<string>());
}
SetValue (as opposed to SetCurrentValue) sets a so-called local value to the Names property. When you assign a Binding as in the second case, this is also considered a local value with the same precedence as the one set in the constructor.
However, in the first case, the Binding is set in a DataTemplate, where it doesn't count as a local value. Since it has lower precedence, it does not replace the initial value.
More details here: Dependency Property Value Precedence

C# VS : Factoring code into UserControl, using ObservableCollection, and consuming it with Binding

I am factoring some code into UserControls which parameters are bound when consumed. I am meeting difficulties with the use of ObservableCollection as a DependencyProperty.
The example showing the difficulty is a project consisting in a MainWindow with two DependencyProperty:
one representing a String (named "Data") and
another one representing an ObservableCollection (named "Origin");
and a UserControl (named UserControl1) exposing two similar DependencyProperty (named resp. "Liste" and "Noun").
The MainWindow contains a TextBlock which Text is bound to "Data" and a ComboBox which ItemsSource is bound to "Origin". Both are working fine.
Both controls are factored into UserControl1, with the DependencyProperty "Liste" and "Noun" acting as intermediate, and UserControl1 is consumed in MainWindow.
Each DataContext (of MainWindow and of UserControl1) is set to "this".
The trouble is while the factored TextBlock (within UserControl1) is working and showing the content of "Data", the factored ComboBox is not working and its DropDown is empty.
The code of MainWindow.xaml is:
<Window x:Class="ChainedBindingUserControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350" Width="525"
xmlns:Local="clr-namespace:ChainedBindingUserControl"
>
<StackPanel>
<TextBlock Text="{Binding Data}"
Width="150"
/>
<ComboBox ItemsSource="{Binding Origin}"
Width="150"
/>
<Label Content="--------------------------------------------------"
Width="200"
/>
<Local:UserControl1 Liste="{Binding Origin}"
Noun="{Binding Data}"
Height="50" Width="150"
/>
</StackPanel>
</Window>
Its code behind is :
namespace ChainedBindingUserControl
{
public partial class MainWindow : Window
{
public ObservableCollection<String> Origin
{
get { return (ObservableCollection<String>)GetValue(OriginProperty); }
set { SetValue(OriginProperty, value); }
}
public static readonly DependencyProperty OriginProperty =
DependencyProperty.Register("Origin", typeof(ObservableCollection<String>), typeof(MainWindow),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public String Data
{
get { return (String)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(String), typeof(UserControl1),
new FrameworkPropertyMetadata("Blablabla", FrameworkPropertyMetadataOptions.AffectsRender));
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
ObservableCollection<String> zog = new ObservableCollection<String>();
zog.Add("A");
zog.Add("B");
zog.Add("C");
Origin = zog;
}
}
}
The file UserControl1.xaml is :
<UserControl x:Class="ChainedBindingUserControl.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Name="root"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<TextBlock Text="{Binding Noun}"
/>
<ComboBox ItemsSource="{Binding Liste}"
/>
</StackPanel>
</UserControl>
Its code behind is :
namespace ChainedBindingUserControl
{
public partial class UserControl1 : UserControl
{
public ObservableCollection<String> Liste
{
get { return (ObservableCollection<String>)GetValue(ListeProperty); }
set { SetValue(ListeProperty, value); }
}
public static readonly DependencyProperty ListeProperty =
DependencyProperty.Register("Liste", typeof(ObservableCollection<String>), typeof(UserControl1),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public String Noun
{
get { return (String)GetValue(NounProperty); }
set { SetValue(NounProperty, value); }
}
public static readonly DependencyProperty NounProperty =
DependencyProperty.Register("Noun", typeof(String), typeof(UserControl1),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender));
public UserControl1()
{
InitializeComponent();
this.DataContext = this;
}
}
}
`
EDIT
According to the pieces of information and snippets provided on http://sshumakov.com/2012/11/13/how-to-create-dependency-properties-for-collections/ , I changed the code behind of UserControl1 into
public partial class UserControl1 : UserControl
{
public IList Liste
{
get { return (List<String>)GetValue(ListeProperty); }
set { SetValue(ListeProperty, value); }
}
public static readonly DependencyProperty ListeProperty =
DependencyProperty.Register("Liste", typeof(IList), typeof(UserControl1),
new FrameworkPropertyMetadata(new List<String>(), FrameworkPropertyMetadataOptions.AffectsRender));
public String Noun
{
get { return (String)GetValue(NounProperty); }
set { SetValue(NounProperty, value); }
}
public static readonly DependencyProperty NounProperty =
DependencyProperty.Register("Noun", typeof(String), typeof(UserControl1),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender));
public UserControl1()
{
InitializeComponent();
this.DataContext = this;
SetValue(ListeProperty, new List<String>());
}
}
but it is still not working.
The trouble doesn't come from the DataContext since the TextBlock works as expected.
The trouble here is specific: why a DependecyProperty acting as an intermediate for Binding is working when the property is of type String while it doesn't work when it is of type ObservableCollection (or List, etc).
Thanks in advance for any explanation.
Your problem is in the UserControl's xaml, here:
<TextBlock Text="{Binding Noun}"
/>
<ComboBox ItemsSource="{Binding Liste}"
/>
These binding expressions are attempting to locate Noun and Liste properties on the DataContext of your UserControl, not on the UserControl itself. You need to specify a different target. Since you've already named your UserControl element, you can replace the bindings with this:
<TextBlock Text="{Binding ElementName=root, Path=Noun}"
/>
<ComboBox ItemsSource="{Binding ElementName=root, Path=Liste}"
/>
Imagine that you are creating control that has property that accepts collection:
public class CustomControl : Control
{
public IEnumerable<string> Items { get; set; }
}
If you want property Items to act as binding target you must change it to be dependency property:
public class CustomControl : Control
{
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IEnumerable<string>), typeof (CustomControl), new PropertyMetadata(new List<string>()));
public IEnumerable<string> Items
{
get { return (IEnumerable<string>) GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
}
As you can see, we changed this property to dependency property and supplied new instance of List class as default parameter. As it turned out, this default value will be used on class level (i.e. it will be created only once and each instance of CustomControl will have reference to the same collection). Therefore, we need one modification:
public class CustomControl : Control
{
public CustomControl()
{
Items = new List<string>();
}
}
Now you can use this control and supply value for Items property via binding:
<Grid>
<DependencyPropertiesCollection:CustomControl Items="{Binding ItemsSource}"/>
</Grid>
Currently this control has one limitation – Items property can’t be filled directly in XAML like this code does:
<Grid>
<DependencyPropertiesCollection:CustomControl>
<DependencyPropertiesCollection:CustomControl.Items>
<System:String>Item 1</System:String>
<System:String>Item 2</System:String>
<System:String>Item 3</System:String>
<System:String>Item 4</System:String>
<System:String>Item 5</System:String>
</DependencyPropertiesCollection:CustomControl.Items>
</DependencyPropertiesCollection:CustomControl>
</Grid>
To fix this, you need to change property type from IEnumerable to IList:
public class CustomControl : Control
{
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof (IList), typeof (CustomControl), new PropertyMetadata(new List<string>()));
public IList Items
{
get { return (IList)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public CustomControl()
{
Items = new List<string>();
}
}
Credits:-
http://sshumakov.com/2012/11/13/how-to-create-dependency-properties-for-collections/

Categories

Resources