Validation not working for custom usercontrol combobox - c#

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>

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();
}

Binding issue in ItemsControl within a UserControl in WPF

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.

WPF UserControl Properties

In the code below, I have a UserControl that contains an ellipse and a textblock. I'd like to create a reusable control that I can bind to that allows me to set the text based on a string, and changes the fill color between Red/Green based on a boolean.
I can do this now by digging deep into the markup and using some complex binding, but I want to reuse this control in a list and it seemed easier to create a control for the purpose. However, I am not sure where to go next and if I should be creating dependency-properties that tie to the values for Fill and Text, or what.
<UserControl
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"
mc:Ignorable="d"
x:Class="Herp.Derp.View.DeliveryStatusIndicator"
x:Name="UserControl"
d:DesignWidth="91" d:DesignHeight="35">
<Grid x:Name="LayoutRoot">
<StackPanel Orientation="Horizontal">
<Ellipse Width="35" Height="35" Fill="Green">
<Ellipse.OpacityMask>
<VisualBrush Stretch="Fill" Visual="{StaticResource appbar_location_circle}"/>
</Ellipse.OpacityMask>
</Ellipse>
<TextBlock Style="{StaticResource Heading2}"
VerticalAlignment="Center" Margin="3,0,0,0">
<Run Text="FRONT"/>
</TextBlock>
</StackPanel>
</Grid>
</UserControl>
here how you can achieve this
UserControl
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static readonly DependencyProperty FrontTextProperty = DependencyProperty.Register( "FrontText", typeof(string),typeof(UserControl1), new FrameworkPropertyMetadata(string.Empty));
public string FrontText
{
get { return (string)GetValue(FrontTextProperty); }
set {
SetValue(FrontTextProperty, value);
frontBlock.Text = value;
}
}
public static readonly DependencyProperty EllipseStateProperty = DependencyProperty.Register("EllipseState", typeof(bool), typeof(UserControl1), new FrameworkPropertyMetadata(false));
public bool EllipseState
{
get { return (bool)GetValue(EllipseStateProperty); }
set
{
SetValue(EllipseStateProperty, value);
if (value)
{
ellipse.Fill = new SolidColorBrush( Colors.Green);
}
else
{
ellipse.Fill = new SolidColorBrush(Colors.Red);
}
}
}
}
MainWindow
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 EllipseState="{Binding yourProperty }"/>
<CheckBox Content="CheckBox" HorizontalAlignment="Left" Margin="207,94,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
Yes, to create properties that "parent" XAML can assign bindings to, you need to create aDependencyProperty for each field that you want to bind to.
You would then bind your user control xaml to the backing property for the DP.
Here's what I got for a working solution:
public partial class DeliveryStatusIndicator : UserControl
{
public DeliveryStatusIndicator()
{
InitializeComponent();
}
public static readonly DependencyProperty DeliveryDescriptionProperty = DependencyProperty.Register("DeliveryDescription", typeof(string), typeof(DeliveryStatusIndicator), new FrameworkPropertyMetadata("Default", DescriptionChangedEventHandler));
private static void DescriptionChangedEventHandler(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DeliveryStatusIndicator)d).Desc.Text = (string)e.NewValue;
}
public string DeliveryDescription
{
get { return (string)GetValue(DeliveryDescriptionProperty); }
set
{
SetValue(DeliveryDescriptionProperty, value);
}
}
public static readonly DependencyProperty DeliveryStatusProperty = DependencyProperty.Register("DeliveryStatus", typeof(bool), typeof(DeliveryStatusIndicator), new FrameworkPropertyMetadata(false, StatusChangedEventHandler));
private static void StatusChangedEventHandler(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DeliveryStatusIndicator)d).Indicator.Fill = (bool)e.NewValue ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
}
public bool DeliveryStatus
{
get { return (bool)GetValue(DeliveryStatusProperty); }
set
{
SetValue(DeliveryStatusProperty, value);
}
}
}

WPF Composite Controls

I'm trying to create a reusable UserControl in WPF that has a Label and a TextBox. I want to add properties to my UserControl to bubble up the Text fields of both child controls up to the parent for easy binding. I read that I need to a little bit of hocus pocus by adding owners to DependencyProperties. Here is my code now. It seems close but not quite right. Any ideas?
Here is the Xaml:
<UserControl x:Class="MAAD.AircraftExit.Visual.LabelTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="20" Width="300">
<DockPanel>
<TextBlock Text="{Binding Path=Label, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" DockPanel.Dock="Left" TextAlignment="Right" Width="122" />
<TextBlock Text=": " DockPanel.Dock="Left"/>
<TextBox Text="{Binding Path=Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</DockPanel>
</UserControl>
And the code behind:
public partial class LabelTextBox : UserControl
{
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(LabelTextBox));
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(LabelTextBox));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(LabelTextBox.TextProperty, value); }
}
public LabelTextBox()
{
InitializeComponent();
ClearValue(HeightProperty);
ClearValue(WidthProperty);
}
}
Edit: Here is the final working code. I switched over to relative source binding.
Binding is really the way to go:
XAML:
<UserControl x:Class="testapp.LabelTextBox "
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300" x:Name="This">
<DockPanel>
<TextBlock DockPanel.Dock="Left" TextAlignment="Right" Width="70" Name="label" Text="{Binding Label, ElementName=This}" />
<TextBlock Text=": " DockPanel.Dock="Left" />
<TextBox Name="textBox" Text="{Binding Text, ElementName=This}" />
</DockPanel>
Code Behind:
public partial class LabelTextBox : UserControl
{
public LabelTextBox()
{
InitializeComponent();
Label = "Label";
Text = "Text";
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(LabelTextBox), new FrameworkPropertyMetadata(LabelPropertyChangedCallback));
private static void LabelPropertyChangedCallback(DependencyObject controlInstance, DependencyPropertyChangedEventArgs args)
{
}
public string Label
{
get { return (string) GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(LabelTextBox), new FrameworkPropertyMetadata(TextPropertyChangedCallback));
private static void TextPropertyChangedCallback(DependencyObject controlInstance, DependencyPropertyChangedEventArgs args)
{
}
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(LabelTextBox.TextProperty, value); }
}
}
I haven't looked into exactly why your implementation isn't working, but I don't really understand why you're doing it that way. Why not just define the dependency properties you need on the UserControl and then bind to them?
public static readonly DependencyProperty LabelTextProperty = ...;
And then in your XAML:
<Label Content="{Binding LabelText}"/>

Categories

Resources