Custom ComboBox throw exception when change ItemsSource - c#

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

Related

WPF Inculding a UserControl in another UserControl with multiple bindings on DependencyProperty [duplicate]

I wrote a customcontrol. It is a textbox with a button which opens a OpenFileDialog.
The Text property of the TextBox is bound to my dependency property "FileName". And if the user selects a file via the OpenFileDialog, i set the result to this property.
The TextBox gets the right value through binding.
But now my problem. For my view I'm using a ViewModel. So I have a Binding to my DependencyProperty "FileName" to the property in my ViewModel.
After changing the "FileName" property (changes direct to the textbox or selecting a file via the dialog), the viewmodel property doesn't update.
CustomControl.xaml.cs
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
namespace WpfApplication1.CustomControl
{
/// <summary>
/// Interaction logic for FileSelectorTextBox.xaml
/// </summary>
public partial class FileSelectorTextBox
: UserControl, INotifyPropertyChanged
{
public FileSelectorTextBox()
{
InitializeComponent();
DataContext = this;
}
#region FileName dependency property
public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register(
"FileName",
typeof(string),
typeof(FileSelectorTextBox),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnFileNamePropertyChanged),
new CoerceValueCallback(OnCoerceFileNameProperty)));
public string FileName
{
get { return (string)GetValue(FileNameProperty); }
set { /*SetValue(FileNameProperty, value);*/ CoerceFileName(value); }
}
private bool _shouldCoerceFileName;
private string _coercedFileName;
private object _lastBaseValueFromCoercionCallback;
private object _lastOldValueFromPropertyChangedCallback;
private object _lastNewValueFromPropertyChangedCallback;
private object _fileNameLocalValue;
private ValueSource _fileNameValueSource;
private static void OnFileNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FileSelectorTextBox)
{
(d as FileSelectorTextBox).OnFileNamePropertyChanged(e);
}
}
private void OnFileNamePropertyChanged(DependencyPropertyChangedEventArgs e)
{
LastNewValueFromPropertyChangedCallback = e.NewValue;
LastOldValueFromPropertyChangedCallback = e.OldValue;
FileNameValueSource = DependencyPropertyHelper.GetValueSource(this, FileNameProperty);
FileNameLocalValue = this.ReadLocalValue(FileNameProperty);
}
private static object OnCoerceFileNameProperty(DependencyObject d, object baseValue)
{
if (d is FileSelectorTextBox)
{
return (d as FileSelectorTextBox).OnCoerceFileNameProperty(baseValue);
}
else
{
return baseValue;
}
}
private object OnCoerceFileNameProperty(object baseValue)
{
LastBaseValueFromCoercionCallback = baseValue;
return _shouldCoerceFileName ? _coercedFileName : baseValue;
}
internal void CoerceFileName(string fileName)
{
_shouldCoerceFileName = true;
_coercedFileName = fileName;
CoerceValue(FileNameProperty);
_shouldCoerceFileName = false;
}
#endregion FileName dependency property
#region Public Properties
public ValueSource FileNameValueSource
{
get { return _fileNameValueSource; }
private set
{
_fileNameValueSource = value;
OnPropertyChanged("FileNameValueSource");
}
}
public object FileNameLocalValue
{
get { return _fileNameLocalValue; }
set
{
_fileNameLocalValue = value;
OnPropertyChanged("FileNameLocalValue");
}
}
public object LastBaseValueFromCoercionCallback
{
get { return _lastBaseValueFromCoercionCallback; }
set
{
_lastBaseValueFromCoercionCallback = value;
OnPropertyChanged("LastBaseValueFromCoercionCallback");
}
}
public object LastNewValueFromPropertyChangedCallback
{
get { return _lastNewValueFromPropertyChangedCallback; }
set
{
_lastNewValueFromPropertyChangedCallback = value;
OnPropertyChanged("LastNewValueFromPropertyChangedCallback");
}
}
public object LastOldValueFromPropertyChangedCallback
{
get { return _lastOldValueFromPropertyChangedCallback; }
set
{
_lastOldValueFromPropertyChangedCallback = value;
OnPropertyChanged("LastOldValueFromPropertyChangedCallback");
}
}
#endregion FileName dependency property
private void btnBrowse_Click(object sender, RoutedEventArgs e)
{
FileDialog dlg = null;
dlg = new OpenFileDialog();
bool? result = dlg.ShowDialog();
if (result == true)
{
FileName = dlg.FileName;
}
txtFileName.Focus();
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged
}
}
CustomControl.xaml
<UserControl x:Class="WpfApplication1.CustomControl.FileSelectorTextBox"
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"
d:DesignHeight="23" d:DesignWidth="300">
<Border BorderBrush="#FF919191"
BorderThickness="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="80" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBox Name="txtFileName"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Grid.Column="0"
Text="{Binding FileName}" />
<Button Name="btnBrowse"
Click="btnBrowse_Click"
HorizontalContentAlignment="Center"
ToolTip="Datei auswählen"
Margin="1,0,0,0"
Width="29"
Padding="1"
Grid.Column="1">
<Image Source="../Resources/viewmag.png"
Width="15"
Height="15" />
</Button>
</Grid>
</Border>
</UserControl>
Using in a view:
<Window x:Class="WpfApplication1.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
xmlns:controls="clr-namespace:WpfApplication1.CustomControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding Files}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="File name" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<controls:FileSelectorTextBox FileName="{Binding .}" Height="30" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<ListBox ItemsSource="{Binding Files}" Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And the ViewModel:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
internal class MainViewModel
: INotifyPropertyChanged
{
public MainViewModel()
{
Files = new ObservableCollection<string> { "test1.txt", "test2.txt", "test3.txt", "test4.txt" };
}
#region Properties
private ObservableCollection<string> _files;
public ObservableCollection<string> Files
{
get { return _files; }
set
{
_files = value;
OnPropertyChanged("Files");
}
}
#endregion Properties
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged Members
}
}
Is there any wrong using of the dependency property?
Note: The problem only occurs in DataGrid.
You need to set binding Mode to TwoWay, because by default binding works one way, i.e. loading changes from the view model, but not updating it back.
<controls:FileSelectorTextBox FileName="{Binding FileName, Mode=TwoWay}" Height="30" />
Another option is to declare your custom dependency property with BindsTwoWayByDefault flag, like this:
public static readonly DependencyProperty FileNameProperty =
DependencyProperty.Register("FileName",
typeof(string),
typeof(FileSelectorTextBox),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Also when you change your custom dependency property from inside your control use SetCurrentValue method instead of directly assigning the value using property setter. Because if you assign it directly you will break the binding.
So, instead of:
FileName = dlg.FileName;
Do like this:
SetCurrentValue(FileNameProperty, dlg.FileName);
Change as following:
<TextBox Name="txtFileName"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Grid.Column="0"
Text="{Binding FileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

Binding properties from CustomControl to page in WPF

I want to bind properties from CustomControl to my page, then back to CustomControl and calculate quantity of pages and display it in list.
My code look like.
CustomControl
public partial class CustomControl : UserControl
public CustomControl()
{
InitializeComponent()
}
public int PageSelected
{
get
{
return (int)GetValue(PageSelectedProperty);
}
set
{
SetValue(PageSelectedProperty, value);
}
}
public static readonly DependencyProperty PageSelectedProperty = DependencyProperty.Register("PageSelected", typeof(int), typeof(CustomControl), new PropertyMetadata(null));
public int RecordsPerPage
{
get
{
return (int)GetValue(RecordsPerPageProperty);
}
set
{
SetValue(RecordsPerPageProperty, value);
}
}
public static readonly DependencyProperty RecordsPerPageProperty = DependencyProperty.Register("RecordsPerPage", typeof(int), typeof(CustomControl), new PropertyMetadata(null));
public IList<int> RecordsPerPageList
{
get
{
return (IList<int>)GetValue(RecordsPerPageListProperty);
}
set
{
SetValue(RecordsPerPageListProperty, value);
}
}
public static readonly DependencyProperty RecordsPerPageListProperty = DependencyProperty.Register("RecordsPerPageList", typeof(List<int>), typeof(CustomControl), new PropertyMetadata(null));
public int RecordsCount
{
get
{
return (int)GetValue(RecordsCountProperty);
}
set
{
SetValue(RecordsCountProperty, value);
CreatePagesList();
}
}
public static readonly DependencyProperty RecordsCountProperty = DependencyProperty.Register("RecordsCount", typeof(int), typeof(CustomControl), new PropertyMetadata(null));
public IList<int> PagesList
{
get
{
return (IList<int>)GetValue(PagesListProperty);
}
set
{
SetValue(PagesListProperty, value);
}
}
public static readonly DependencyProperty PagesListProperty = DependencyProperty.Register("PagesList", typeof(List<int>), typeof(CustomControl), new PropertyMetadata(null));
public int PagesCount
{
get
{
return (int)GetValue(PagesCountProperty);
}
set
{
SetValue(PagesCountProperty, value);
}
}
public static readonly DependencyProperty PagesCountProperty = DependencyProperty.Register("PagesCount", typeof(int), typeof(CustomControl), new PropertyMetadata(null));
Custom Control xaml
<UserControl x:Class="Mtrx.CustomControls.CustomControl"
mc:Ignorable="d"
d:DesignHeight="90" Width="200">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="90" />
</Grid.RowDefinitions>
<ComboBox Width="40" Height="20" Grid.Column="1" Margin="0,5,0,5"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=PagesList}"
SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=PageSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
HorizontalContentAlignment="Center"/>
<ComboBox Width="40" Height="20" Grid.Column="9" Margin="0,5,0,5"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=RecordsPerPageList, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=RecordsPerPage, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
HorizontalContentAlignment="Center"/>
</Grid>
</UserControl>
Page xaml.cs
public class PageVievModel:AbstractPage
public int RowsCount
{
get
{
return _rowsCount;
}
set
{
if (value != _rowsCount)
{
_rowsCount = value;
RaisePropertyChanged("RowsCount");
}
}
}
private int _rowsCount; //we get it from other place
public int DGRecordsMax
{
get
{
return _dgRecordsMax;
}
set
{
if (value != _dgRecordsMax)
{
_dgRecordsMax = value;
if (value > 0)
{
DataGridRecordsMaxCount = value.ToString();
Settings.Default.Save();
}
RaisePropertyChanged("DGRecordsMax");
}
}
}
private int _dgRecordsMax;
public IList<int> DGRecordsMaxList
{
get
{
return _dGRecordsMaxList;
}
set
{
if (_dGRecordsMaxList != value)
{
_dGRecordsMaxList = value;
RaisePropertyChanged("DGRecordsMaxList");
}
}
}
private IList<int> _dGRecordsMaxList = new List<int>();
public IList<int> PagesList
{
get
{
return _pagesList;
}
set
{
if (_pagesList != value)
{
_pagesList = value;
RaisePropertyChanged("PagesList");
}
}
}
private IList<int> _pagesList = new List<int>();
public int PagesCount
{
get
{
return _pagesCount;
}
set
{
if (value != _pagesCount)
{
_pagesCount = value;
RaisePropertyChanged("PagesCount");
}
}
}
private int _pagesCount;
public IList<int> CurrentPageList
{
get
{
return _currentPageList;
}
set
{
if (_currentPageList != value)
{
_currentPageList = value;
RaisePropertyChanged("CurrentPageList");
}
}
}
private IList<int> _currentPageList;
Page xaml
<UserControl x:Class="SomeClass"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="800"
IsEnabled="{Binding AllowInput, Converter={StaticResource AnyToBooleanConverter}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DockPanel>
<SomeClass:CustomControl Width="280" Height="190"
RecordsCount="{Binding RowsCount, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
RecordsPerPage="{Binding DGRecordsMax, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay }"
RecordsPerPageList="{Binding DGRecordsMaxList, Mode=TwoWay}"
PagesCount="{Binding PagesCount, Mode=TwoWay}"
PageSelected="{Binding CurrentPage, Mode=TwoWay}"
PagesList="{Binding PagesList, Mode=TwoWay}"
RecordsFrom="{Binding RecordsFrom, Mode=TwoWay}"
RecordsTo="{Binding RecordsTo, Mode=TwoWay}"
DockPanel.Dock="Right"
VerticalAlignment="Bottom"/>
</WrapPanel.Resources>
</WrapPanel>
</Grid>
</UserControl>
When I am trying to run my program there are empty lists, earlier when I just kept more properties in Page it was working fine.
I would be greatful for help. It's hard to understand for me how make this two way bindable properties.
I tried to make an example using your code.
For me it worked if I changed both IList and List to ObservableCollection, e.g.
using System.Collections.ObjectModel;
....
public ObservableCollection<int> PagesList
{
get
{
return (ObservableCollection<int>)GetValue ( PagesListProperty );
}
set
{
SetValue ( PagesListProperty, value );
}
}
public static readonly DependencyProperty PagesListProperty = DependencyProperty.Register("PagesList", typeof(ObservableCollection<int>), typeof(CustomControl), new PropertyMetadata(null));
Note that I changed both the Property definition and the DependencyProperty definition.
Your code is a bit messy, for example you have a DockPanel tag which is closed with /WrapPanel, which obviously will not compile.
<DockPanel>
</WrapPanel>

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.

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>

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

Categories

Resources