Custom Control Dependency Property Binding - wpf

I am going insane trying to get this to work with even the most basic example. I cannot for the life of me get binding to work. Here is a super easy example that is not working for me. I MUST be doing something incorrect.
My Custom Control in my control library assembly:
public class TestControl : Control
{
public static readonly DependencyProperty TestPropProperty =
DependencyProperty.Register("TestProp", typeof(string), typeof(TestControl), new UIPropertyMetadata(null));
public string TestProp
{
get { return (string)GetValue(TestPropProperty); }
set { SetValue(TestPropProperty, value); }
}
static TestControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl), new FrameworkPropertyMetadata(typeof(TestControl)));
}
}
And its XAML template:
<Style TargetType="{x:Type local:TestControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBlock Text="Testing..." />
<Label Content="{Binding TestProp}" Padding="10" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's the XAML consuming the control in a wpf window with a reference to my control library:
<Grid>
<ItemsControl Name="mylist">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:TestControl TestProp="{Binding Path=Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
And here's the code behind:
public partial class Test2 : Window
{
public class TestObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
private int _id;
public int id
{
get { return _id; }
set { _id = value; OnPropertyChanged("id"); }
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; OnPropertyChanged("Name"); }
}
}
public Test2()
{
InitializeComponent();
mylist.ItemsSource = new TestObject[]
{
new TestObject(){ id = 1, Name = "Tedd" },
new TestObject(){ id = 2, Name = "Fred" },
new TestObject(){ id = 3, Name = "Jim" },
new TestObject(){ id = 4, Name = "Jack" },
};
}
}
Running this example gives me four instances of the control, however I only see the "Testing..." TextBlock on each. My label is never bound. What am I misunderstanding and doing incorrectly?

You haven't set the proper binding source. You would either have to set RelativeSource:
<Label Content="{Binding TestProp, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
Or use TemplateBinding:
<Label Content="{TemplateBinding TestProp}"/>

Related

Binding a listbox inside a custom control .Cannot Bind it

Binding a listbox inside a custom control
I am building a custom control which will have a listbox inside.I have posted here the simplified version
I cannot seem to be displaying a property of my bound viewModel."Surname".
Could you see what I am doing wrong?
Generic.Xaml (inside CustomControl)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1.SimpleList">
<Style TargetType="{x:Type local:SimpleListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SimpleListControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid ShowGridLines="False"
DataContext="{Binding RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type local:SimpleListControl}}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" Grid.Column="0" Grid.Row="0">
<ListBox Name="lstLeft"
MinHeight="200"
ItemsSource="{Binding LeftSideList, RelativeSource={RelativeSource TemplatedParent}}"
DisplayMemberPath="{Binding DisplayMemberPath, RelativeSource={RelativeSource TemplatedParent}}"
SelectionMode="Extended"
IsSynchronizedWithCurrentItem="True"
SelectedIndex="0" />
</StackPanel>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
public class SimpleListControl : Control
{
static SimpleListControl()
{
DefaultStyleKeyProperty
.OverrideMetadata(typeof(SimpleListControl),
new FrameworkPropertyMetadata(typeof(SimpleListControl)));
LeftSideListProperty = DependencyProperty.Register("LeftSideList",
typeof(IEnumerable),
typeof(SimpleListControl),
new PropertyMetadata(OnLeftItemsSourceChanged));
DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath",
typeof(string),
typeof(SimpleListControl),
new UIPropertyMetadata(string.Empty));
}
public readonly static DependencyProperty LeftSideListProperty;
public ObservableCollection<object> LeftSideList
{
get{return new ObservableCollection<object>{GetValue(LeftSideListProperty)};}
set { SetValue(LeftSideListProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty;
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
private static void OnLeftItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//TODO.
}
}
CustomerView
<Window x:Class="WpfApp1.Views.CustomersView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:simpleList="clr-namespace:WpfCustomControlLibrary1.SimpleList;assembly=WpfCustomControlLibrary1"
xmlns:viewModels="clr-namespace:WpfApp1.ViewModels"
Title="CustomersViewq" Height="300" Width="300">
<Window.Resources>
<viewModels:CustomersViewModel x:Key="ViewModel" />
</Window.Resources>
<Grid>
<simpleList:SimpleListControl Name="SimpleListControl1"
LeftSideList="{Binding Source={StaticResource ViewModel},Path=AvailableCustomers}"
DisplayMemberPath="Surname" Margin="0,24,0,12" />
</Grid>
</Window>
CustomersViewModel
public class CustomersViewModel:ViewModelBase
{
public CustomersViewModel()
{
availableCustomers = new ObservableCollection<CustomerViewModel>();
availableCustomers.Add(new CustomerViewModel { FirstName = "Jo1", Surname = "Bloggs1" });
availableCustomers.Add(new CustomerViewModel { FirstName = "Jo2", Surname = "Bloggs2" });
availableCustomers.Add(new CustomerViewModel { FirstName = "Jo3", Surname = "Bloggs3" });
availableCustomers.Add(new CustomerViewModel { FirstName = "Jo4", Surname = "Bloggs4" });
}
private ObservableCollection<CustomerViewModel> availableCustomers;
public ObservableCollection<CustomerViewModel> AvailableCustomers
{
get { return availableCustomers; }
set
{
if (availableCustomers == value) return;
availableCustomers = value;
OnPropertyChanged("AvailableCustomers");
}
}
}
CustomerViewModel
public class CustomerViewModel : ViewModelBase
{
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
if (firstName == null) return;
firstName = value;
OnPropertyChanged("FirstName");
}
}
private string surname;
public string Surname
{
get { return surname; }
set
{
if (surname == null) return;
surname = value;
OnPropertyChanged("Surname");
}
}
}
Whatever you trying to achieve, it seems you are over complicating things.
However if you need to display Surname you need to set it in the DisplayMemberPath as DisplayMemberPath="Surname".
Here's a tutorial on Custom Controls: How to Create Custom Control.
Below is a link for creating re-useable User control:
Creating Re-useable User Controls.

WPF MVVM IDataErrorInfo

i have an app in MVVM pattern ; it contains one textbox and one button;
i add a validation that if textbox is empty , textbox color change to red;
and at this point i want that the button enable be false till the user
enter character in textbox and then button enable change to true;
my XAML Code is:
<Window.Resources>
<ControlTemplate x:Key="ErrorTemplate">
<DockPanel LastChildFill="True">
<Border BorderBrush="Pink" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Margin="10" Text="{Binding ValidateInputText,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, ValidatesOnExceptions=True}"
Validation.ErrorTemplate="{StaticResource ErrorTemplate}">
</TextBox>
<Button Margin="10" Grid.Row="2" Command="{Binding ValidateInputCommand}"/>
and my command class is:
public class RelayCommand : ICommand
{
private Action WhatToExcute;
private Func<bool> WhenToExecute;
public RelayCommand(Action what,Func<bool>when )
{
WhatToExcute = what;
WhenToExecute = when;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return WhenToExecute();
}
public void Execute(object parameter)
{
WhatToExcute();
}
}
and my viewmodel is:
public class ViewModel : IDataErrorInfo , INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
Product product = new Product();
public ViewModel()
{
ValidateInputCommand = new RelayCommand(action, valid);
}
public void action()
{
}
public bool valid()
{
if (product.Name == null)
return false;
return true;
}
public string ValidateInputText
{
get { return product.Name; }
set {
product.Name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(ValidateInputText));
}
}
}
public RelayCommand ValidateInputCommand { get; set; }
public string this[string columnName]
{
get
{
if ("ValidateInputText" == columnName)
{
if (String.IsNullOrEmpty(ValidateInputText))
{
return "Please enter a Name";
}
}
return "";
}
}
public string Error
{
get
{
throw new NotImplementedException();
}
}
}
now when i run my app , the color of text box is red and when i enter a
character it change to normal that is ok but the
button enable is false and does not change
so i could not click button.
what should i do?
The CanExecuteChanged event on your RelayCommand never gets fired. Therefore the button to which the command has been bound will never re-evaluate its IsEnabled status. Your view model could notify the command on changes in its valid status and the command should in turn raise its CanExecuteChanged event.

Cannot set Expression. It is marked as 'NonShareable' and has already been used

iv'e created a custom control deriving from ItemsControl .
public class CustsomItemsControl : ItemsControl
{ }
XAML :
<local:CustsomSelectorControl ItemsSource="{Binding People}">
<local:CustsomSelectorControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}"/>
</DataTemplate>
</local:CustsomSelectorControl.ItemTemplate>
</local:CustsomSelectorControl>
The Control Template :
<Style TargetType="{x:Type local:CustsomItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustsomItemsControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
in DataContext :
public MainWindow()
{
InitializeComponent();
People = new ObservableCollection<Person>();
People.Add(new Person("A"));
People.Add(new Person("B"));
People.Add(new Person("C"));
}
private ObservableCollection<Person> _people;
public ObservableCollection<Person> People
{
get { return _people; }
set
{
_people = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("People"));
}
}
The items are never set , iv'e observed this with snoop ,the ItemsSource property
is marked in RED but there are no BindingErrors , when i delve the BindingExpression
i get an ArgumentExpression:
Cannot set Expression. It is marked as 'NonShareable' and has already been used.
Set the DataContext properly:
public MainWindow()
{
InitializeComponent();
DataContext = this; //This is what you're missing
...
}
Still, you need to have a really strong reason to subclass ItemsControl.

WPF: How to use Style.Triggers

I want to implement (file) Explorer like icon display. The items have date and label.
User should be able to edit the label:
Select an item
Click on label
Label's TextBlock is replaced with TextBox for editing
How to end editing (just for info):
Click anywhere outside of the TextBox
Press Enter keyboard key (by implementing ICommand?)
1st I tried to set the Visibility of TextBlock and TextBox in code found out it is not the 'right' way to to do. Maybe it is possible to edit item's Label using (Data)Triggers?
I can track the OnClickLabelBlock and set selectedMedia.IsEditing = true; but it does not fire the trigger.
Any idea why MediaItem.IsEditing property value change is notifying the DataTrigger? Is it something to do with the order of execution or priority mechanism?
I will pick the answer which guides me to the 'best' architecture to solve it.
Thanks.
XAML:
<Window x:Class="WPFComponents.DailyImages"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Model="clr-namespace:WPFComponents.Model"
Title="Media Items" Height="300" Width="300">
<ListView x:Name="_mediaItemList" ItemsSource="{Binding MediaItems}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="Model:MediaItem">
<Grid Width="80" Margin="4">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Image HorizontalAlignment="Center" Stretch="Uniform" Source="{Binding Path=IconPath}" Width="70" />
<StackPanel Grid.Row="2">
<TextBlock Text="{Binding Path=Date}" TextWrapping="Wrap" />
<TextBlock x:Name="_labelTextBlock" Text="{Binding Path=Label}" TextWrapping="Wrap"
PreviewMouseLeftButtonDown="OnClickLabelBlock">
</TextBlock>
<TextBox x:Name="_labelTextBox" Text="{Binding Path=Label}" Visibility="Collapsed"
TextWrapping="WrapWithOverflow" TextAlignment="Center">
</TextBox>
</StackPanel>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter TargetName="_labelTextBlock" Property="Visibility" Value="Collapsed" />
<Setter TargetName="_labelTextBox" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Source:
public partial class DailyImages
{
public DailyImages()
{
InitializeComponent();
ViewModel.DailyImages dailyImages = new ViewModel.DailyImages();
// DailyImages has ObservableCollection<MediaItem> MediaItems property
_mediaItemList.DataContext = dailyImages;
}
private void OnClickLabelBlock(object sender, MouseButtonEventArgs e)
{
TextBlock notes = sender as TextBlock;
if (notes == null)
return;
MediaItem selectedMedia = notes.DataContext as MediaItem;
if (selectedMedia == null)
{
// TODO: Throw exception
return;
}
_mediaItemList.SelectedItems.Clear();
selectedMedia.IsSelected = true;
selectedMedia.IsEditing = true;
}
public class MediaItem
{
public MediaItem()
{
IsEditing = false;
IsSelected = false;
}
public DateTime Date { get; set; }
public string Label { get; set; }
public string IconPath { get; set; }
public bool IsEditing { get; set; }
public bool IsSelected { get; set; }
}
References:
Dependency Property Value Precedence
Part II: ListView & File Explorer Like Behaviour
MediaItem must implement INotifyPropertyChanged and each of its properties that must be bound, must call RaisePropertyChanged in order for the binding to work correctly. In your case, the Binding on IsEditing has no way to know that the value has changed.
To bind your IsEditing property, WPF has to be notified when it is modified.
Then you have to implement INotifyPropertyChanged in MediaItem. (Or add dependency properties)
public class MediaItem : INotifyPropertyChanged
{
public MediaItem()
{
IsEditing = false;
IsSelected = false;
}
// Use the same pattern for Date, Label & IconPath if these value may change after the MediaItem instance has been added to the collection MediaItems.
public DateTime Date { get; set; }
public string Label { get; set; }
public string IconPath { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private bool isEditing;
public bool IsEditing
{
get { return isEditing; }
set
{
if (isEditing != value)
{
isEditing = value;
OnPropertyChanged("IsEditing");
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Otherwise, your code is correct.

Error Template Design

It seems like I read another question / answer on this site about this issue but I cannot recall what the answer was and now I cannot find the original post.
I am not a fan of the default error template in WPF. I understand how to change this error template. However, if I add some content to the end of, say, a textbox, the size of the textbox does not change and the added content will (potentially) get clipped. How do I alter the textbox (I believe the correct termonology is adorned element) in this scenario so that nothing gets clipped?
Here is the XAML for the error template:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder />
<TextBlock Foreground="Red" Text="Error..." />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is the XAML for a couple of textboxes in the form:
<StackPanel>
<TextBox Text="{Binding...}" />
<TextBox />
</StackPanel>
Here's a solution adapted from Josh Smith's article on Binding to (Validation.Errors)[0] without Creating Debug Spew.
The trick is to define a DataTemplate to render the ValidationError object and then use a ContentPresenterto display the error message. If there is no error, then the ContentPresenter will not be displayed.
Below, I have shared the code of the sample app that I created.
Here is the XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
SizeToContent="WidthAndHeight"
Title="MainWindow">
<StackPanel Margin="5">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type ValidationError}">
<TextBlock Text="{Binding ErrorContent}" Foreground="White" Background="Red" VerticalAlignment="Center" FontWeight="Bold"/>
</DataTemplate>
</StackPanel.Resources>
<TextBox Name="TextBox1" Text="{Binding Text1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<ContentPresenter Content="{Binding ElementName= TextBox1, Path=(Validation.Errors).CurrentItem}" HorizontalAlignment="Left"/>
<TextBox Name="TextBox2" Text="{Binding Text2, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<ContentPresenter Content="{Binding ElementName= TextBox2, Path=(Validation.Errors).CurrentItem}" HorizontalAlignment="Left"/>
<Button Content="Validate" Click="Button_Click"/>
</StackPanel>
</Window>
The code behind file:
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel _ViewModel = null;
public MainWindow()
{
InitializeComponent();
_ViewModel = new ViewModel();
DataContext = _ViewModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_ViewModel.Validate = true;
_ViewModel.OnPropertyChanged("Text1");
_ViewModel.OnPropertyChanged("Text2");
}
}
}
The ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string _Text1;
public string Text1
{
get { return _Text1; }
set
{
_Text1 = value;
OnPropertyChanged("Text1");
}
}
private string _Text2;
public string Text2
{
get { return _Text2; }
set
{
_Text2 = value;
OnPropertyChanged("Text2");
}
}
public bool Validate { get; set; }
#region INotifyPropertyChanged Implemenation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region IDataErrorInfo Implementation
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
string errorMessage = string.Empty;
if (Validate)
{
switch (columnName)
{
case "Text1":
if (Text1 == null)
errorMessage = "Text1 is mandatory.";
else if (Text1.Trim() == string.Empty)
errorMessage = "Text1 is not valid.";
break;
case "Text2":
if (Text2 == null)
errorMessage = "Text2 is mandatory.";
else if (Text2.Trim() == string.Empty)
errorMessage = "Text2 is not valid.";
break;
}
}
return errorMessage;
}
}
#endregion
}
}

Resources