I have encountered an issue where the bindings are not properly set on one of my user controls if that control is not visible when it is initialized. I have duplicated the issue with the following dumbed-down controls:
public class Test3 : Control
{
static Test3()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Test3), new FrameworkPropertyMetadata(typeof(Test3)));
}
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(string),
typeof(Test3), new UIPropertyMetadata("test3 default text"));
}
public class Test2 : Control
{
static Test2()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Test2), new FrameworkPropertyMetadata(typeof(Test2)));
}
public FrameworkElement Test3Control
{
get { return (FrameworkElement)GetValue(Test3ControlProperty); }
set { SetValue(Test3ControlProperty, value); }
}
public static readonly DependencyProperty Test3ControlProperty =
DependencyProperty.Register("Test3Control", typeof(FrameworkElement),
typeof(Test2), new UIPropertyMetadata(null));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(string), typeof(Test2),
new UIPropertyMetadata("test2 default text"));
}
public partial class Test1 : UserControl
{
public Test1()
{
InitializeComponent();
}
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(string),
typeof(Test1), new UIPropertyMetadata("test1 default text"));
}
XAML for usercontrol:
<UserControl x:Class="Test.Test1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dummy="clr-namespace:WpfTestApplication.Test"
Name="ucThis">
<UserControl.Resources>
<Style TargetType="{x:Type test:Test2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type test:Test2}">
<GroupBox Header="Test2">
<StackPanel>
<TextBox IsEnabled="False" Text="{TemplateBinding Test}"/>
<GroupBox Header="Test3">
<ContentPresenter Content="{TemplateBinding Test3Control}"/>
</GroupBox>
</StackPanel>
</GroupBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<test:Test2 Test="{Binding ElementName=ucThis,Path=Test}">
<test:Test2.Test3Control>
<test:Test3 Test="{Binding ElementName=ucThis,Path=Test}">
<test:Test3.Template>
<ControlTemplate TargetType="{x:Type test:Test3}">
<TextBox IsEnabled="False" Text="{TemplateBinding Test}"/>
</ControlTemplate>
</test:Test3.Template>
</test:Test3>
</test:Test2.Test3Control>
</test:Test2>
</Grid>
</UserControl>
... and XAML for main window (the guts of it, anyway):
<DockPanel>
<StackPanel>
<TextBox Name="tbInput"/>
<Expander Header="Initially Visible" IsExpanded="True">
<test:Test1 Test="{Binding ElementName=tbInput, Path=Text}" />
</Expander>
<Expander Header="Initially Collapsed" IsExpanded="False">
<test:Test1 Test="{Binding ElementName=tbInput, Path=Text}" />
</Expander>
</StackPanel>
</DockPanel>
I would expect that whatever text is entered into the textbox ("tbInput") would be displayed in the Test2 and Test3 boxes - and indeed it is for both instances of Test2 but only for the Test3 that is initially visible. The Test3 that is initially collapsed always displays the default text, even if it is visible when the text is entered.
I've tried to investigate this using Snoop, but the issue corrects itself when I evaluate the relevant parts of the tree w/ Snoop so it hasn't been much help.
What is causing this behavior? How can I correct it? Where can I read more about it?
UPDATE:
By watching the output window I discovered this error message:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=ucThis'. BindingExpression:Path=Test; DataItem=null; target element is 'Dummy3' (Name=''); target property is 'Test' (type 'String')
By handling the loaded and initialized events on these controls I can see that this error occurs after Dummy1 and Dummy2 have loaded at startup. Dummy 3 doesn't load until it is made visible.
I believe the problem is that, since the template has not been applied for the collapsed Test3 instance, it has not been inserted into the visual tree. It is therefore not within the name scope of the outer Test1 instance when the binding is created, and thus cannot resolve the name ucThis specified for ElementName.
You can deal with this by adding Test3Control to the logical tree of Test2. Try modifying the dependency property definition as follows:
public static readonly DependencyProperty Test3ControlProperty =
DependencyProperty.Register("Test3Control", typeof(FrameworkElement),
typeof(Test2),
new UIPropertyMetadata(null, OnTest3ControlPropertyChanged));
private static void OnTest3ControlPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var source = (Test2)d;
var oldValue = e.OldValue as FrameworkElement;
var newValue = e.NewValue as FrameworkElement;
if (oldValue != null)
source.RemoveLogicalChild(oldValue);
if (newValue != null)
source.AddLogicalChild(newValue);
}
In most cases when you add a new UIElement-based property to a control, you will want to ensure it gets added to the logical tree. This does not happen automatically. For that matter, it does not get added to the visual tree automatically either. In this case it is only loaded into the visual tree because it is explicitly inserted into the template.
Take a look at the internals of WPF's core controls, like Decorator (from which Border derives) and its Child property to see what kind of plumbing may be required when defining new control types.
Also note how many "child control" properties are not dependency properties. It's easy to run into problems with Visual-based dependency properties, particularly when you attempt to change them via setters or animate them. I find it's best to discourage developers from misusing the properties and creating headaches for themselves by simply exposing child controls as regular CLR properties.
I am not sure about your specific problem, but I'd move the Test2's implicit style definition to the Generic.xaml module which has to be in the "Themes" folder. The framework will scan automatically that file to look for implicit styles.
Related
I want to ask about the right way if I want to create Bindable user control consisting of two controls. I am not sure about what I am doing - whether I do it correctly , because I run into some problems.
Here is what I am trying to do:
Lets call this control ucFlagControl . Create new , custom user control ...
Its purpose is to show Color interpretation of logic ( True/ False ) value in variable , type of Bool.
What I used to do before was that I use Rectangle, and Bind FillProperty to boolean value using Converter
What I did to make it works was , that I made a usercontrol , and put rectangle and label inside
than I added this code:
public partial class ucStatusFlag : UserControl
{
public ucStatusFlag()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set
{
SetValue(LabelContentProperty, value);
OnPropertyChanged("LabelContent");
}
}
///in case that I use integer or array
public int BitIndex
{
get { return (int)GetValue(BitIndexProperty); }
set
{
SetValue(BitIndexProperty, value);
OnPropertyChanged("BitIndex");
}
}
public string BindingSource
{
get { return (string)GetValue(BindingSourceProperty); }
set
{
SetValue(BindingSourceProperty, value);
OnPropertyChanged("BindingSource");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ucStatusFlag), new PropertyMetadata("LabelContent"));
public static readonly DependencyProperty BitIndexProperty =
DependencyProperty.Register("BitIndex", typeof(int), typeof(ucStatusFlag), new PropertyMetadata(0));
public static readonly DependencyProperty BindingSourceProperty =
DependencyProperty.Register("(BindingSource", typeof(string), typeof(ucStatusFlag), new PropertyMetadata(""));
private void StatusFlag_Loaded(object sender, RoutedEventArgs e)
{
if (BindingSource.Length > 0)
{
Binding bind = new Binding();
string s = LabelContent;
int i = BitIndex;
bind.Converter = new StatusToColor();
bind.Path = new PropertyPath(BindingSource);
bind.ConverterParameter = BitIndex.ToString();
bind.Mode = BindingMode.OneWay;
bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
recStatusBit.SetBinding(Rectangle.FillProperty, bind);
}
}
private class StatusToColor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
byte bDataWordIdx;
byte bDataBitIdx;
Byte.TryParse((string)parameter, out bDataBitIdx);
if (Object.ReferenceEquals(typeof(UInt16[]), value.GetType()))
{
UInt16[] uiaData = (UInt16[])value;
bDataWordIdx = (byte)uiaData[0];
if ((uiaData[bDataBitIdx / 16] >> (bDataBitIdx % 16) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
else if (Object.ReferenceEquals(typeof(UInt16), value.GetType()))
{
UInt16 uiaData = (UInt16)value;
if (((uiaData >> bDataBitIdx) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return 0;
}
}
}
}
Than I realized that I can easily bind content and I do not have to create public static readonly DependencyProperty LabelContentProperty
but just property
public new string Content
{
get { return (string)label.Content; }
set
{
SetValue(label.Content, value);
OnPropertyChanged("Content");
}
}
this overrides the original content so I am able to Bind and/or assign the text of the label in upper level - in e.g. MainWindow.xaml where this user control is put
First question is if this is in this case OK or if there is some background I am not aware of and I should even such small controls do in different way - I would like to make dll. from it an load it to toolbox - I tested it works. And than use it in for example stack panel .
Second question is that I have problem with a rectangle "Fill" property . I am not able to bind that property like I bind content .
I know that the rectangle is derived from Shape class so I am not sure if it has something to do with this.
If I am able to do the inner binding or connection same as in
Content
I can remove the converters than and just bind it in e.g. MainWindow.xaml file (using the converter and converter parameter )
But FillProperty does not work for me so I am not sure about my point of view .
Thank you for suggestions
EDIT:
well I am sorry but I did not catch all you want to say in a comment below. Could you please explain closer ?
I know that the code above is not the right way to do it ... ?
Or can you post any article about it ?
my actual code is like this:
In a user control ... I removed all the code from code behind ...
' <Label x:Name="lStatusBit" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" Margin="2,1,17,2" />
<Rectangle x:Name="recStatusBit" Margin="0,3,1,7" />'
Content property works, I cant see Rectangle , and rectangle fill property ...
Other problem is if I fill in Content property in XAML where my uc is placed , Rectangle disappears .
I know I'm a year late to the party, but I'll answer incase anyone else comes across this.
My Suggestions
You should use a TextBlock control instead of Label controls if you want to display pure text. Labels have a content element which is re-rendered/computed many more times than a TextBlock's simple Text property.
You should avoid using magic strings, e.g. "LabelContent". You should use the C# nameof() expression when referencing property names. For example:
I use lambda expressions to clean up the code a bit, but this is just preference.
public string LabelContent
{
get => (string)GetValue(LabelContentProperty);
set => SetValue(LabelContentProperty, value);
}
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register(
nameof(LabelContent),
typeof(string),
typeof(ucStatusFlag),
new PropertyMetadata("Default Value"));
This will prevent runtime errors due to mistyped text, will allow you to jump to the property's reference, will make refactoring easier, and will make debugging easier by giving you a compile error that's easy to find (if the property doesn't exist).
I don't think you need the rectangle. If you're just trying to change the background color of the text area you can use a DataTrigger or make a converter.
DataTrigger Example
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<!-- The default value -->
<Setter Property="Background" Value="Transparent" />
<!-- Your trigger -->
<Style.Triggers>
<DataTrigger Binding="{Binding SomeBooleanValue}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
A DataTrigger is a quick and easy way to style a control by binding to a property on your ViewModel (assuming you're using the MVVM structure), but there are some cons - like reusing the same style on a different View whose ViewModel's properties are different. You'd have to rewrite the entire styling again.
Lets turn it into a reusable control where we can (1) specify a highlight background color, and (2) use a boolean to determine whether the control is highlighted.
Template Controls vs UserControls
I make my templated controls in a separate C# class file and put the control's styling in another separate resource dictionary file instead of using a UserControl.
These templated controls can consist of several other controls to make a single reusable control.
It's my understanding that UserControls are meant to use multiple templated controls (e.g. a TextBox) and link their interactions together to perform a specific way.
I don't think these controls are meant to be reusable in separate unrelated projects - they display data depending on your ViewModel which can be situational.
If you want to extend your custom control in the future via inheritance, then using a UserControl will make things difficult.
Here's what a few of my controls look like in the solution explorer:
Solution Files Snippet
The ExpansionPanel control in the snippet is an Expander with additional functionalities/properties.
The NavButton is a Button with additional functionalities/properties also.
I have a NavigationView UserControl that uses both of those controls to create something much larger than a templated control.
It sounds like you want to create a reusable templated control.
Creating a Custom Control
Here are the basic steps:
Create a "Themes" folder at the root of your project. It must be at the root of your project and spelling does matters.
Create a Generic.xaml Resource Dictionary file in the "Themes" folder. It must be directly under the "Themes" folder and spelling does matters.
This is where you store the default themes for your custom controls.
The template style for your control will automatically be added to the Generic.xaml file when you add a Custom Control template to your project.
<Style TargetType="{x:Type local:Example}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Example}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Personally, I like to have separate .xaml file for each control, and then I merge it into the Generic.xaml resource dictionary. This is just for organization purposes.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- Control template styles -->
<ResourceDictionary Source="pack://application:,,,/Themes/ExpansionPanel.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/NavButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocument.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocumentToolBar.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextEditor.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/HighlightTextBlock.xaml" />
<!-- etc... -->
</ResourceDictionary.MergedDictionaries>
<!-- Other styles or whatever -->
</ResourceDictionary>
It's important to note that order does matter if you have controls that depend on other controls.
Merge the Generic.xaml file into your App.xaml file.
<Application>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
<ResourceDictionary Source="pack://application:,,,/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
</ResourceDictionary>
</Application.Resources>
</Application>
Why not just merge the control templates in the App.xaml file directly? WPF looks directly for the Generic.xaml file for custom type themes. App.xaml is also application specific and wouldn't be able to be usable in other applications if you used the library as a control library.
Create a .cs file using the built in Custom Control template OR a standard C# class file.
Your control's .cs file would resemble something similar to...
public class HighlightTextBlock : Control
{
#region Private Properties
// The default brush color to resort back to
public Brush DefaultBackground;
#endregion
static HighlightTextBlock()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(typeof(HighlightTextBlock)));
}
// Get the default background color and set it.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
DefaultBackground = Background;
}
#region Dependency Properties
/// <summary>
/// The text to display.
/// </summary>
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(string.Empty));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>
/// Whether or not the background should be highlighted.
/// </summary>
// This uses a callback to update the background color whenever the value changes
public static readonly DependencyProperty HighlightProperty = DependencyProperty.Register(
nameof(Highlight), typeof(bool),
typeof(HighlightTextBlock), new PropertyMetadata(false, HighlightPropertyChangedCallback));
public bool Highlight
{
get => (bool)GetValue(HighlightProperty);
set => SetValue(HighlightProperty, value);
}
/// <summary>
/// The highlight background color when <see cref="Highlight"/> is true.
/// </summary>
public static readonly DependencyProperty HighlightColorProperty = DependencyProperty.Register(
nameof(HighlightColor), typeof(Brush),
typeof(HighlightTextBlock), new PropertyMetadata(null));
public Brush HighlightColor
{
get => (Brush)GetValue(HighlightColorProperty);
set => SetValue(HighlightColorProperty, value);
}
#endregion
#region Callbacks
// This is the callback that will update the background
private static void HighlightPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var target = (HighlightTextBlock)dependencyObject;
if (target.Highlight)
target.Background = target.HighlightColor;
else
target.Background = target.DefaultBackground;
}
#endregion
}
Create a ResourceDictionary.xaml file to store your control's template and style OR add it directly in Generic.xaml.
Your .xaml file would look something like...
<Style x:Key="HighlightTextBlock" TargetType="{x:Type ctrl:HighlightTextBlock}">
<!-- Default setters... -->
<!-- Define your control's design template -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctrl:HighlightTextBlock}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<!--
I only bound the Text and Background property in this example
Make sure to bind other properties too.. like Visibility, IsEnabled, etc..
-->
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
Set the default style for the control
The above style has a key, so controls won't use that style
unless the style is explicitly set.
e.g.
<ctrl:HighlightTextBlock Style={StaticResource HighlightTextBlock} />
The reason I used a key above is to allow extending/reusing that default style.
If a key wasn't present then you wouldn't be able to reference it in
another style.
-->
<Style TargetType="{x:Type ctrl:HighlightTextBlock}" BasedOn="{StaticResource HighlightTextBlock}" />
Add a reference to the control's resource dictionary in Generic.xaml, like in step 2's code snippet.
Usage:
I'm binding the IsChecked property to a IsHighlighted property on my ViewModel.
You can bind it to whatever.
<StackPanel>
<ToggleButton IsChecked="{Binding IsHighlighted}" Content="{Binding IsHighlighted}"
Width="100" Height="35" Margin="5"/>
<ctrl:HighlightTextBlock Background="Transparent" HighlightColor="Red"
Text="HELLO WORLD!!!" Highlight="{Binding IsHighlighted}"
Width="100" Height="35" HorizontalAlignment="Center" />
</StackPanel>
On False Snippet
On True Snippet
Your controls may look a bit different - I'm using a custom dark theme.
Finally I found this working :
XAML
<UserControl x:Class="ucStatusFlag"
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="17" d:DesignWidth="100"
x:Name="StatusFlag">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle x:Name="recStatusBit" Grid.Column="0" Stroke="Black" Width="11" Fill="{Binding ElementName=StatusFlag, Path=RectangleColor}" Margin="0,2,0.2,3.8" />
<Label Height="17" x:Name="lStatusBit" Foreground="Black" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" Margin="5,0,0,1" Content="{Binding ElementName=StatusFlag, Path=LabelContent}" />
</Grid>
C#
public partial class ucStatusFlag : UserControl
{
public ucStatusFlag()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set
{
SetValue(LabelContentProperty, value);
}
}
public SolidColorBrush RectangleColor
{
get { return (SolidColorBrush)GetValue(RectangleColorProperty); }
set
{
SetValue(RectangleColorProperty, value);
}
}
public static readonly DependencyProperty RectangleColorProperty =
DependencyProperty.Register("RectangleColor", typeof(SolidColorBrush), typeof(ucStatusFlag), new PropertyMetadata(Brushes.Gold));
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ucStatusFlag), new PropertyMetadata("LabelContent"));
}
Binding in Another Project :
<ucStatusFlag HorizontalAlignment="Left" Height="18" Margin="154,224,0,0" VerticalAlignment="Top" Width="100" LabelContent="ABC" RectangleColor="{Binding RectangleColorPropertyInProject}"/>
Where RectangleColorPropertyInProject is Property In certain project view model
Attempting to write a custom control which permits a collection of children content. The end goal is to have some functional filtering of the child elements based on custom attached dependency properties; however, before getting that far, any attempts I've made to collect and re-display child UIElements have ended in exceptions during XAML parsing/display.
I have a class CustomFilter : Control which has an associated default style in a nearby xaml file. It has a dependency property for Children, which currently collect the child elements in a UIElementCollection.
I believe my problem may be how I am attempting to render the collection of children. The only way I know of looping through content in XAML involves using an ItemsControl, to which I am passing my Children collection as the ItemsSource, which feels rather backwards (using UIElements as data models?)
Afterwards, we attempt to render the elements via a content presenter inside of the ItemTemplate. I've seen in many other examples which render control children using content presenters, so I expect this is probably at least partially correct (though all only using single elements).
The sample class
[ContentProperty(nameof(Children))]
public class CustomFilter : Control
{
static CustomFilter()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomFilter), new FrameworkPropertyMetadata(typeof(CustomFilter)));
}
public CustomFilter()
{
Children = new UIElementCollection(this, this);
}
public static readonly DependencyProperty ChildrenProperty = DependencyProperty.Register(nameof(Children), typeof(UIElementCollection), typeof(CustomFilter), new FrameworkPropertyMetadata(RefreshFilter));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public UIElementCollection Children
{
get { return (UIElementCollection)GetValue(ChildrenProperty); }
private set { SetValue(ChildrenProperty, value); }
}
}
The sample template
<Style TargetType="{x:Type local:CustomFilter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomFilter}">
<StackPanel>
<ItemsControl ItemsSource="{TemplateBinding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The use case
<local:CustomFilter>
<TextBlock Text="X"/>
<TextBlock Text="Y"/>
<TextBlock Text="Z"/>
</local:CustomFilter>
Attempting to run use this control whatsoever causes it to fail when it reaches the ItemsControl, without reaching the ItemTemplate, with the following exception:
ArgumentException: Specified Visual is already a child of another Visual or the root of a CompositionTarget.
I've attempted to create a wrapper class for the Children, passing one child to each instance of the wrapper, and binding to that as an ItemsSource - it successfully loops through the ItemTemplate, but attempting to use the ContentPresenter there on the wrapped child element provides me with the following exception instead:
ArgumentException: Must disconnect specified child from current parent Visual before attaching to new parent Visual.
Why are you using an UIElementCollection instead of a List<UIElement>? You are not supposed to bind to an UIElementCollection in a template.
Your example should work if you define a List<UIElement> dependency property:
[ContentProperty(nameof(Children))]
public class CustomFilter : Control
{
static CustomFilter()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomFilter), new FrameworkPropertyMetadata(typeof(CustomFilter)));
}
public CustomFilter()
{
SetValue(ChildrenPropertyKey, new List<UIElement>());
}
private static readonly DependencyPropertyKey ChildrenPropertyKey =
DependencyProperty.RegisterReadOnly(
nameof(Children),
typeof(List<UIElement>),
typeof(CustomFilter),
new FrameworkPropertyMetadata(new List<UIElement>())
);
public static readonly DependencyProperty ChildrenProperty =
ChildrenPropertyKey.DependencyProperty;
public List<UIElement> Children
{
get { return (List<UIElement>)GetValue(ChildrenProperty); }
}
}
This worked in UWP but I can't get the image to show using WPF XAML.
I start by defining a UserControl which binds the path of the image file:
<Grid Height="70" Width="70">
<Border Style="{StaticResource border}">
<Button Style="{StaticResource button}">
<Image Source="{Binding prop_image}"/>
</Button>
</Border>
</Grid>
I define the dependency property as:
public static readonly DependencyProperty prop_image =
DependencyProperty.Register("prop_image_path", typeof(string),
typeof(user_control), null);
public string prop_image_path
{
get { return (string)GetValue(prop_image); }
set { SetValue(prop_image, value); }
}
I then try to consume it as:
<local:user_control Grid.Column="1" Grid.Row="2"
prop_image_path="/Assets/my.png"/>
which is the exact same as UWP but with Binding instead of x:bind. when I create a button and set the image it works . . . but it doesn't display the alpha channel(which I guess means I have to use an alpha mask and have two files.) aside from this, moving a bunch of stuff from UWP to WPF XAML was a snap.
At first, you are using the wrong property path in {Binding prop_image}, which should be {Binding prop_image_path}. Since this Binding is in a UserControl's XAML to one of its own properties, you should also specify the source object of the Binding to be the UserControl instance, like:
<Image Source="{Binding prop_image_path,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
Besides that, the WPF dependency property system requires that you adhere to naming conventions for the dependency property identifier field.
It must be named like the property with a Property suffix:
public static readonly DependencyProperty prop_image_pathProperty =
DependencyProperty.Register(
"prop_image_path",
typeof(string),
typeof(user_control),
null);
You may also notice that your naming scheme is a little uncommon. According to widely accepted conventions, C#/.NET type and property names should use Pascal Casing, i.e.
public class MyUserControl
{
public static readonly DependencyProperty ImagePathProperty =
DependencyProperty.Register(
nameof(ImagePath),
typeof(string),
typeof(MyUserControl));
public string ImagePath
{
get { return (string)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
}
I wrote a CustomControl that supports direct content. When I nest a TabControl in it, there's no tab item selected on launch. It happens only when using ItemsSource. Anyone know what's wrong?
Sample implementation
In MainWindow.xaml:
<Window.DataContext>
<local:VM/>
</Window.DataContext>
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TabControl ItemsSource="{Binding Data}"/>
<local:CustomControl1 Grid.Column="1">
<TabControl ItemsSource="{Binding Data}" />
</local:CustomControl1>
</Grid>
VM.cs
class VM
{
public List<int> Data { get; set; } = new List<int>{ 1, 2, 3, 4 };
}
CustomControl1:
[ContentProperty("Content")]
public class CustomControl1 : Control
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public FrameworkElement Content
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(FrameworkElement), typeof(CustomControl1), new PropertyMetadata(null));
}
In Generic.xaml:
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
After some time of poking around I've found a solution, but I am not sure of the exact reasons behind this behavior.
I've modified your example and compared the behavior when the TabControl was put inside a ContentControl and a CustomControl1. Predictably, the first tab in case of ContentControl was selected, but in case of CustomControl1 it was not. After inspecting the ContentControl source code (in particular the ContentControl.OnContentChanged method) we can see that what it does is it sets its content as its logical child.
I then confirmed that setting the TabControl as the CustomControl1's logical child does the trick (but I'm not sure why, as I mentioned). So the minimal solution to your problem is to handle the logical relation between your control and its content in the property changed callback, e.g.:
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(
"Content",
typeof(FrameworkElement),
typeof(CustomControl1),
new PropertyMetadata(null)
{
PropertyChangedCallback = OnContentChanged
});
private static void OnContentChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = (CustomControl1)d;
if (e.OldValue != null)
control.RemoveLogicalChild(e.OldValue);
if (e.NewValue != null)
control.AddLogicalChild(e.NewValue);
}
Note though that this lacks some checks (e.g. if new value already does have a logical parent or the control is a part of a template), so you might want to simply copy the code from the referenced source, or fallback to deriving your control from ContentControl altogether.
I have two custom controls/visuals and I need an Orientation property on both. In both cases, the default should be "Horizontal" but the user of the control/visual should be able to specify Orientation="Vertical" to arrange the components of the control/visual vertically. What I have works great on my ImageButton control, but not so well on my HeaderedLabel visual. Although both of them compile fine, Intellisense doesn't like one of them. Here's an example of their use...
<Visuals:ImageButton Image="Icons/ok.png" Content="Normal Content"/>
<Visuals:ImageButton Image="Icons/ok.png" Content="Vertical Content" Orientation="Vertical"/>
<Visuals:HeaderedLabel Header="Normal Header" Content="Normal Content"/>
<Visuals:HeaderedLabel Header="Vertical Header" Content="Vertical Content" Orientation="Vertical"/>
...which produces the following when rendered inside a vertical StackPanel:
So it does what I want, but the problem is this: While Intellisense recognizes the possible options for Orientation for the ImageButton, it does not recognize the possible options for Orientation for the HeaderedLabel. And while the code compiles & runs fine, there's a persistent error in the Visual Studio "Error List" pane: "'Vertical' is not a valid value for property 'Orientation'.", and there's a blue squiggly line under the text Orientation="Vertical" for the second HeaderedLabel in my xaml example above.
Here are the relevant files:
// File 'ImageButton.cs'
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Visuals
{
public class ImageButton : Button
{
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton),
new FrameworkPropertyMetadata(typeof(ImageButton)));
}
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
// Note that for ImageButton, I can just say 'Orientation.Horizontal' and
// the compiler resolves that to System.Windows.Controls.Orientation...
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation),
typeof(ImageButton), new UIPropertyMetadata(Orientation.Horizontal));
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource),
typeof(ImageButton), new UIPropertyMetadata(null));
}
}
.
// File 'HeaderedLabel.cs'
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Visuals
{
public class HeaderedLabel : Control
{
static HeaderedLabel()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(HeaderedLabel),
new FrameworkPropertyMetadata(typeof(HeaderedLabel)));
}
public object Header
{
get { return (object)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public object Orientation
{
get { return (object)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(object), typeof(HeaderedLabel),
new UIPropertyMetadata(null));
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(HeaderedLabel),
new UIPropertyMetadata(null));
// Note that for HeaderedLabel, unlike ImageButton, I have to specify the fully-
// qualified name of 'Orientation.Horizontal', otherwise the compiler resolves it
// to Visuals.HeaderedLabel.Orientation and gives a compiler error...
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(System.Windows.Controls.Orientation),
typeof(HeaderedLabel), new UIPropertyMetadata(System.Windows.Controls.Orientation.Horizontal));
}
}
.
<!-- File 'Generic.xaml' -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Visuals"
xmlns:bind="clr-namespace:Visuals.BindingConverters">
<Style TargetType="{x:Type local:ImageButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ImageButton}">
<Button>
<StackPanel Orientation="{TemplateBinding Orientation}">
<Image Source="{TemplateBinding Image}"/>
<ContentPresenter/>
</StackPanel>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:HeaderedLabel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:HeaderedLabel}">
<StackPanel Orientation="{TemplateBinding Orientation}">
<StackPanel Orientation="Horizontal">
<ContentControl Content="{TemplateBinding Header}" />
<TextBlock Text=":" />
</StackPanel>
<ContentControl Content="{TemplateBinding Content}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Any ideas why the compiler would resolve Orientation.Horizontal to System.Windows.Controls.Orientation.Horizontal for the ImageButton, but not for the HeaderedLabel? And more importantly, any ideas why Intellisense can't figure out the options for HeaderedLabel.Orientation?
BTW, I'm using VisualStudio 2012 and .NET Framework 4.0.
All of your properties, including the Orientation property, are declared as having the type object.
You should have this instead:
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
The XAML editor should be able to correctly accept values of type Orientation if you declare the property correctly. Otherwise, it will attempt to assign a string value of "Vertical" to the property, which when passed to the SetValue() method will fail, because the DependencyProperty object itself was initialized with Orientation as the valid type and it has no way to convert from the string value to an Orientation value.
If you declare the property correctly, then WPF will understand automatically that it needs to convert the string value shown in the XAML to an Orientation value for the property (i.e. parse the string value as the appropriate enum type), and in that case the initialization should work.