How to set custom properties in an inherited control in xaml - c#

I built a custom control TabularListView based on ListView with a new Property, an ObservableCollection of TabularListViewHeaderColumn.
TabularListView:
class TabularListView : ListView
{
public TabularListView()
{
Columns = new ObservableCollection<TabularListViewHeaderColumn>();
}
public ObservableCollection<TabularListViewHeaderColumn> Columns
{
get { return (ObservableCollection<TabularListViewHeaderColumn>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<TabularListViewHeaderColumn>), typeof(TabularListView), new PropertyMetadata(new ObservableCollection<TabularListViewHeaderColumn>()));
}
TabularListViewHeaderColumn:
class TabularListViewHeaderColumn : ContentControl
{
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(TabularListViewHeaderColumn), new PropertyMetadata(string.Empty));
public string MappingName
{
get { return (string)GetValue(MappingNameProperty); }
set { SetValue(MappingNameProperty, value); }
}
public static readonly DependencyProperty MappingNameProperty =
DependencyProperty.Register("MappingName", typeof(string), typeof(TabularListViewHeaderColumn), new PropertyMetadata(string.Empty));
}
I try to use it like shown below, setting the TabularListViewHeaderColumns directly in XAML:
<Controls:TabularListView>
<Controls:TabularListView.Header>
<ItemsControl>
<DataTemplate>
<Button Content="{Binding Title}" Width="{Binding Width}"
FontWeight="Bold"/>
</DataTemplate>
</ItemsControl>
</Controls:TabularListView.Header>
<Controls:TabularListView.Columns>
<Controls:TabularListViewHeaderColumn Title="Test" MappingName="Inactive" Width="60"/>
<Controls:TabularListViewHeaderColumn Title="Articlenumber" MappingName="DeviceType.ArticleNo" Width="170"/>
<Controls:TabularListViewHeaderColumn Title="Serialnumber" MappingName="SerialNo" Width="170"/>
<Controls:TabularListViewHeaderColumn Title="Inventorynumber" MappingName="EndCustNo" Width="170"/>
<Controls:TabularListViewHeaderColumn Title="Description" MappingName="DeviceType.Description1" Width="300"/>
</Controls:TabularListView.Columns>
</Controls:TabularListView>
Usualy when creating a regular Custom Control like in this example https://social.technet.microsoft.com/wiki/contents/articles/32828.uwp-how-to-create-and-use-custom-control.aspx , I need to bind the respected property to the DependencyProperty, but since I only inherit from ListView and don't have any Xaml, I'm quiet confused how to do so.

Related

XAML: Property on DependecyObject not updating when it is an item in an attached collection

I have a DependencyObject that's inside an attached dependency property (that's a collection). Binding to that object does not work for some reason.
In my example, I am binding two things, a basic attached property (local:CollHolder.BasicProperty) and a regular dependent property (local:MyItem.MyData) - both are bound to the Text of a TextBox control.
The XAML looks like this:
<ListView ItemsSource="{x:Bind Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<StackPanel x:Name="stack" local:CollHolder.BasicProperty="{Binding ElementName=text, Path=Text}" VerticalAlignment="Center" HorizontalAlignment="Center" >
<TextBox Text="" x:Name="text"/>
<local:CollHolder.Coll>
<local:MyItem MyData="{Binding ElementName=text, Path=Text}"/>
</local:CollHolder.Coll>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When changes happen the Text property, they propagate to the attached property, but not to the dependency property.
CollHolder:
public class CollHolder : DependencyObject
{
public static readonly DependencyProperty BasicPropertyProperty =
DependencyProperty.RegisterAttached("BasicProperty", typeof(string), typeof(CollHolder), new PropertyMetadata("", DPC));
public static readonly DependencyProperty CollProperty =
DependencyProperty.RegisterAttached("Coll", typeof(Coll), typeof(CollHolder), new PropertyMetadata(null));
public static Coll GetColl(DependencyObject obj)
{
var coll = (Coll)obj.GetValue(CollProperty);
if (coll == null)
{
obj.SetValue(CollProperty, coll = new Coll());
}
return coll;
}
public static void SetColl(DependencyObject obj, Coll value)
{
obj.SetValue(CollProperty, value);
}
public static string GetBasicProperty(DependencyObject obj)
{
return (string)obj.GetValue(BasicPropertyProperty);
}
public static void SetBasicProperty(DependencyObject obj, string value)
{
obj.SetValue(BasicPropertyProperty, value);
}
private static void DPC(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Basic Property changed");
}
}
MyItem:
public class MyItem : DependencyObject
{
public string MyData
{
get { return (string)GetValue(MyDataProperty); }
set { SetValue(MyDataProperty, value); }
}
// Using a DependencyProperty as the backing store for MyData. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyDataProperty =
DependencyProperty.Register("MyData", typeof(string), typeof(MyItem), new PropertyMetadata("", DPC));
private static void DPC(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("CHANGED!!");
}
}
And the collection is pretty simple:
public class Coll : List<MyItem>
{
}
When changes happen the Text property, they propagate to the attached property, but not to the dependency property.
<TextBox Text="" x:Name="text"/>
<local:CollHolder.Coll>
<local:MyItem MyData="{Binding ElementName=text, Path=Text}"/>
</local:CollHolder.Coll>
The TextBox and local:MyItem in the same DataContext, you could use {x:Bind } to get the text value directly.
<TextBox Text="{x:Bind }" x:Name="text" />
<local:CollHolder.Coll>
<local:MyItem MyData="{x:Bind }" />
</local:CollHolder.Coll>
Update
I tried to replace MyItem with Control to test whether DependencyProperty works in Binding mode. It works as expected. So, you could use Control as MyItem's base class.
public class MyItem : Control
{
public string MyData
{
get { return (string)GetValue(MyDataProperty); }
set { SetValue(MyDataProperty, value); }
}
// Using a DependencyProperty as the backing store for MyData. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyDataProperty =
DependencyProperty.Register("MyData", typeof(string), typeof(MyItem), new PropertyMetadata("", DPC));
private static void DPC(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("CHANGED!!");
}
}

Binding change not pushed back to gui

I have the following code somewhere (using C# and UWP):
public static readonly DependencyProperty SupportsFineChannelProperty = DependencyProperty.Register(
"SupportsFineChannel", typeof(bool), typeof(ChannelGroupView),
new PropertyMetadata(default(bool)));
public bool SupportsFineChannel {
get { return (bool) GetValue(SupportsFineChannelProperty); }
set { SetValue(SupportsFineChannelProperty, value); }
}
and in my gui I use it with
<TextBox Text="{Binding FineChannel, Mode=TwoWay}" IsEnabled="{Binding SupportsFineChannel, Mode=TwoWay}" />
<CheckBox IsChecked="{Binding SupportsFineChannel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Supports Fine Channel</CheckBox>
Now I have a (for my understanding) unusual behaviour. If I load the frame while the SupportFineChannel is false, the Textbox is disabled. Check. If I do the same while the bool is true, the Textbox is enabled. Check, binding works.
Now the strange part is that the TextBox won't change its IsEnabled value if I change it trough the checkbox.
With the debugger I confirmed:
The Evaluated Binding value of the CheckBox gets changed
The binding properly propagates to the Variable in the backend which changes as expected
The evaluated textbox binding value though does not take note of the changed DepProp.
I have set the binding to OneWay and TwoWay, both do not alter this behaviour. Why does the DepProp not propagate the change back to the bindings?
EDIT
I just tested it with different properties. Fact is, no dependency property pushes values back to the gui when it changes... srsly?
EDIT2
INotifyPropertyChanged does work though.. Is there some different behaviour with DepProps on UWP that I'm not aware of?
EDIT3
It was easier than I thought to create a repro sample. So nothing crazy with my setups..
For anyone interested, here it is.
In case the link goes down sometime or you don't trust me;
I created a new UWP blank project with target version 10.0, 15063, Min Version 10.0 10586
Create a DataView and a ItemView
public class DataView : DependencyObject {
public static readonly DependencyProperty ItemStuffProperty = DependencyProperty.Register(
"ItemStuff", typeof(ObservableCollection<ItemView>), typeof(DataView),
new PropertyMetadata(default(ObservableCollection<ItemView>)));
public ObservableCollection<ItemView> ItemStuff {
get { return (ObservableCollection<ItemView>) GetValue(ItemStuffProperty); }
set { SetValue(ItemStuffProperty, value); }
}
public DataView() {
ItemStuff = new ObservableCollection<ItemView>();
}
}
public class ItemView : DependencyObject {
public static readonly DependencyProperty SupportsStuffProperty = DependencyProperty.Register(
"SupportsStuff", typeof(bool), typeof(ItemView), new PropertyMetadata(default(bool)));
public bool SupportsStuff {
get { return (bool) GetValue(SupportsStuffProperty); }
set { SetValue(SupportsStuffProperty, value); }
}
public static readonly DependencyProperty TextStuffProperty = DependencyProperty.Register(
"TextStuff", typeof(string), typeof(ItemView), new PropertyMetadata(default(string)));
public string TextStuff {
get { return (string) GetValue(TextStuffProperty); }
set { SetValue(TextStuffProperty, value); }
}
}
Add the DataView and an example Item to your codebehind
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
"Data", typeof(DataView), typeof(MainPage), new PropertyMetadata(default(DataView)));
public DataView Data {
get { return (DataView) GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public MainPage() {
Data=new DataView();
Data.ItemStuff.Add(new ItemView());
this.InitializeComponent();
}
Set your Datacontext to DataContext="{Binding RelativeSource={RelativeSource Self}, Path=Data}"
Add your Listbox
<ListBox ItemsSource="{Binding ItemStuff}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding TextStuff}" IsEnabled="{Binding SupportsStuff, Mode=OneWay}" />
<CheckBox IsChecked="{Binding SupportsStuff, Mode=TwoWay}"> Supports Stuff</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
???
Profit!

WPF custom composite user controls

First of all, I am a WPF beginner! My approach is potentially not the right way to do what I want so do not hesitate to tell me if that is the case. What I want to do is a composite user control in WPF, using MVVM.
Some classes will do a better presentation than I, here are my view models:
interface IParameter : INotifyPropertyChanged
{
string Name { get; set;}
string Value { get; set;}
}
class TextParameter : ViewModelBase, IParameter
{
private string _value;
public string Name { get; private set; }
public string Value
{
get { return _value; }
set
{
_value = value;
RaisePropertyChanged();
}
}
public TextParameter (string name)
{
this.Name = name;
}
}
class ParameterList : ViewModelBase, IParameter
{
private string _value;
public string Name { get; private set; }
public string Value
{
get { return _value; }
set
{
_value = value;
RaisePropertyChanged();
}
}
ObservableCollection<IParameter> Parameters { get; set; }
public ParameterList (string name, IEnumerable<IParameter> parameters = null)
{
this.Name = name;
this.Parameters = new ObservableCollection<IParameter>(parameters ?? new List<IParameter>());
}
}
I am using MVVM Light, so all the PropertyChanged stuff is managed into ViewModelBase. Also, this is not an exhaustive list of all the parameters, there is some others, more complex but the issue is about these ones.
Here are my custom user controls:
TextParameterControl.xaml:
<UserControl x:Class="Stuff.TextParameterControl" [..] x:Name="parent">
<StackPanel DataContext="{Binding ElementName=parent}" Orientation="Horizontal">
<TextBlock Text="{Binding Path=ParamName, StringFormat='{}{0}:'}" Width="100"></TextBlock>
<TextBox Text="{Binding Path=Value}" Width="100"></TextBox>
</StackPanel>
</UserControl>
TextParameterControl.xaml.cs :
public class TextParameterControl : UserControl
{
#region param name
public string ParamName
{
get { return (string)GetValue(ParamNameProperty); }
set { SetValue(ParamNameProperty, value); }
}
// Using a DependencyProperty as the backing store for ParamName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParamNameProperty =
DependencyProperty.Register("ParamName", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));
#endregion
#region value
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));
#endregion
public TextParameterControl()
{
InitializeComponent();
}
}
ParameterListControl.xaml:
<UserControl x:Class="Stuff.ParameterListControl" [..] x:Name="parent">
<UserControl.Resources>
<DataTemplate x:Key="TextParameterTemplate">
<c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterListTemplate">
<c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
</DataTemplate>
<s:ParameterTemplateSelector x:Key="ParameterSelector"
TextParameterTemplate="{StaticResource TextParameterTemplate}"
ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>
<Expander DataContext="{Binding ElementName=parent}" Header="{Binding Path=ParamName}" IsExpanded="True" ExpandDirection="Down">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Items}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
</StackPanel>
</Expander>
</UserControl>
ParameterListControl.xaml.cs:
public partial class ParameterListControl: UserControl
{
#region param name
public string ParamName
{
get { return (string)GetValue(ParamNameProperty); }
set { SetValue(ParamNameProperty, value); }
}
// Using a DependencyProperty as the backing store for ParamName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParamNameProperty =
DependencyProperty.Register("ParamName", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));
#endregion
#region value
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));
#endregion
#region items
public IList<string> Items
{
get { return (List<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IList<string>), typeof(ParameterListControl), new PropertyMetadata(new List<string>()));
#endregion
public ParameterListControl()
{
InitializeComponent();
}
}
Here is my custom template selector:
class ParameterTemplateSelector : DataTemplateSelector
{
public DataTemplate ParameterListTemplate { get; set; }
public DataTemplate TextParameterTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TextParameter)
{
return this.TextParameterTemplate;
}
else if (item is ParameterList)
{
return this.ParameterListTemplate;
}
throw new Exception(String.Format("This parameter ({0}) is not handled in the application", item.GetType().Name));
}
}
And here is the calling View and ViewModel:
ViewModel:
public class MainViewModel : ViewModelBase
{
public ObservableCollection<IParameter> Parameters { get; set; }
public MainViewModel()
{
this.Parameters = new ObservableCollection<IParameter>();
this.Parameters.Add(new TextParameter("Customer"));
// here I am building my complex composite parameter list
}
View:
<UserControl.Resources>
<DataTemplate x:Key="TextParameterTemplate">
<c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterListTemplate">
<c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
</DataTemplate>
<s:ParameterTemplateSelector x:Key="ParameterSelector"
TextParameterTemplate="{StaticResource TextParameterTemplate}"
ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Parameters}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
When I run the application, the TextParameter in the MainViewModel.Parameters are well loaded (VM.Name and VM.Value properties are well binded to UC.ParamName and UC.Value. Contrariwise, the ParameterList in MainViewModel.Parameters are partially loaded. UC.Name is well binded to the UC.ParamName but the VM.Parameters is not binded to the UC.Items (the UC.DataContext is the VM, the VM.Parameters is well defined, but the UC.Items is desperately null).
Do you have any idea of what I am missing ?
(I am not a native speaker, excuse me if my english hurts you)
I see you have a binding MainViewModel.Parameters -> ParameterListControl.Items but you might be missing the binding from ParameterListControl.Items -> ParameterList.Parameters. (That's assuming ParameterList is the ViewModel for the ParameterListControl - you provide the code for DataContext bindings.)
See the accepted answer on this question. (Ignore the comment on Caliburn.Micro - same solution worked for me in MVVM Light.)
Essentially, in the constructor of ParameterListControl you create an extra binding between the dependency property of the view and the viewmodel's property.
(Also, Dbl is right in the comments that when debugging binding problems, the "unimportant" "plumbing" code that you omitted is very important.)
I finally find out:
The Items dependency property of ParameterListControl was a IList<string>. It was a copy/paste mistake from another UC. I changed it to IEnumerable and everything works fine now:
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IEnumerable), typeof(ParameterListControl), new PropertyMetadata(new List<object>()));
I continued to work on the code and it is now finished and truly composite compared to the sample I posted earlier. If someone is interested in seeing/using this code, you can find it on github.

Labeled ComboBox in Windows 10 Universal App

similar to my Labeled TextBox, which issues are resolved in:
Labeled TextBox in Windows Universal App
I got two issues in my Labeled Combobox, but first the Code:
Generic.xaml:
<Style TargetType="template:LabeledComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="template:LabeledComboBox">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{TemplateBinding Label}" FontWeight="Bold" VerticalAlignment="Center" Margin="10,0" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding ItemsSource}" SelectedIndex="{TemplateBinding SelectedIndex}" SelectedValue="{TemplateBinding SelectedValue}" SelectedValuePath="{TemplateBinding SelectedValuePath}" DisplayMemberPath="{TemplateBinding DisplayMemberPath}" VerticalAlignment="Center" Margin="20,0,10,0" Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
LabeledComboBox.cs:
[TemplatePart(Name = "PART_ComboBox", Type = typeof(ComboBox))]
public sealed class LabeledComboBox : Control, IParameterReturnable
{
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(""));
public string Label
{
get { return GetValue(LabelProperty).ToString(); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(LabeledComboBox), new PropertyMetadata(null));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register("SelectedIndex", typeof(int), typeof(LabeledComboBox), new PropertyMetadata(default(int)));
public int SelectedIndex
{
get { return (int) GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(LabeledComboBox), new PropertyMetadata(null));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValuePathProperty = DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(default(string)));
public string SelectedValuePath
{
get { return GetValue(SelectedValuePathProperty).ToString(); }
set { SetValue(SelectedValuePathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(default(string)));
public string DisplayMemberPath
{
get { return GetValue(DisplayMemberPathProperty).ToString(); }
set { SetValue(DisplayMemberPathProperty, value); }
}
private ComboBox _comboBox;
public LabeledComboBox()
{
this.DefaultStyleKey = typeof(LabeledComboBox);
}
public LabeledComboBox(List<Parameter> parameterList)
{
this.Label = parameterList[0].DisplayName ?? "";
this.ItemsSource = parameterList;
this.SelectedValuePath = "DefaultValue";
this.DisplayMemberPath = "DefaultValue";
this.SelectedIndex = 0;
this.DefaultStyleKey = typeof(LabeledComboBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_comboBox = GetTemplateChild("PART_ComboBox") as ComboBox;
if (_comboBox != null)
{
_comboBox.SelectionChanged += OnComboBoxSelectionChanged;
if (_comboBox.Items != null)
{
this.SelectedIndex = 0;
_comboBox.SelectedValue = _comboBox.Items[this.SelectedIndex];
}
}
}
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedValue = _comboBox.SelectedValue;
}
public string GetKey()
{
return Label;
}
public string GetValue()
{
return SelectedValue.ToString();
}
}
It will be called in two different ways:
Dynamically in C#:
stackPanel.Add(new LabeledComboBox(parameterList));
Static in Xaml:
<templates:LabeledComboBox Label="Kategorien:" ItemsSource="{Binding ElementName=pageRoot, Path=FeedCategories}" DisplayMemberPath="Name" SelectedValuePath="Name" />
As I said before I got two issues with it:
How can I bind the SelectionChangedEvent to access it in Xaml || C#
As you can see, I try to preselect the first Item, which does not work and I don't know how to do it right
Thank you very much for all helpful and well meant answers in advance!
Instead of creating a custom control and recreating all needed dependency properties, I would suggest you use the Header and HeaderTemplate properties of the built in ComboBox, which will be displayed, just like in your LabeledComboBox, above the selection menu. Additionally the SelectionChanged event will be available.
So the usage in XAML would look like the following:
<ComboBox
DisplayMemberPath="Name"
Header="Kategorien:"
ItemsSource="{Binding ElementName=pageRoot, Path=FeedCategories}"
SelectedValuePath="Name"
SelectionChanged="OnSelectionChanged">
<ComboBox.HeaderTemplate>
<DataTemplate>
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding}" />
</DataTemplate>
</ComboBox.HeaderTemplate>
</ComboBox>
But if you don't want to use the above method, to expose the selection changed event in your LabeledComboBox, add the following code:
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedValue = _comboBox.SelectedValue;
this.RaiseSelectionChanged(e);
}
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
private void RaiseSelectionChanged(SelectionChangedEventArgs args)
{
if (SelectionChanged != null)
{
SelectionChanged(this, args);
}
}
Then you can use the created SelectionChanged event from XAML.

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

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

Categories

Resources