I have to create Silverlight User Control with public properties that should be used in inner controls.
public partial class MyControl : UserControl
{
public static readonly DependencyProperty MyCustomProperty =
DependencyProperty.Register(
"MyCustom", typeof(string), typeof(MyControl),
new PropertyMetadata("defaultValue"));
public string MyCustom
{
...
}
I tried several ways to bind, but all fail - dependency property is not seen for some reason.
For example this straightforward binding fails:
<UserControl x:Class="...MyControl"
...
x:Name="mc"
>
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Image Source="{Binding Path=MyCustom, Mode=OneWay, ElementName=mc}" />
</Grid>
</UserControl>
What I'am doing wrong?
What you are doing is not a good pattern. The UserControl does not really "own" the name property. If another UserControl or Page were to place an instanced of your MyControl in its Xaml, it can give it name other than "mc", at which point your code is broken.
Instead use this approach:-
<UserControl x:Class="...MyControl"
>
<Grid x:Name="LayoutRoot" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Image Source="{Binding Path=Parent.MyCustom, Mode=OneWay, ElementName=LayoutRoot}" />
</Grid>
</UserControl>
That out of the way you main problem is that the Image Source property is of type ImageSource not string. You get to use a string literal in Xaml because the Xaml parser does some parser magic the converts the string to an ImageSource. This doesn't happen when using binding.
Change you controls property to:-
public partial class MyControl : UserControl
{
public static readonly DependencyProperty MyCustomProperty =
DependencyProperty.Register(
"MyCustom", typeof(ImageSource), typeof(MyControl),
new PropertyMetadata(null));
[TypeConverter(typeof(ImageSourceConverter))]
public ImageSource MyCustom
{
...
}
Now in another UserControl or Page where you MyControl is hosted you can use a string to assign this MyCustom property. However in code you need to create an instance of something like BitmapImage to assign to this property.
Related
Can we implement dependency property in a class that does not inherit from DependencyObject? If yes what is the difference?
Yes you can. This is a variant of an Attached Property. From How to Create an Attached Property:
If your class is defining the attached property strictly for use on other types, then the class does not have to derive from DependencyObject. But you do need to derive from DependencyObject if you follow the overall WPF model of having your attached property also be a dependency property.
The difference begins in how you define an attached property. Instead of Register, you have to use the RegisterAttached method and you have to define the get and set accessors as static methods with the following naming convention, where PropertyName is the name of the attached property.
public static object GetPropertyName(object target)
public static void SetPropertyName(object target, object value)
Let's look at a simple example. Suppose you want create a button that shows an image and a text side-by side. Unfortunately, a Button only has a single Content. As you do not want to create a custom control right now, you try to solve the issue by creating a content template and an attached property for the path of an image to display.
public static class IconButtonProperties
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source", typeof(string), typeof(IconButtonProperties));
public static void SetSource(UIElement element, string value)
{
element.SetValue(SourceProperty, value);
}
public static string GetSource(UIElement element)
{
return (string) element.GetValue(SourceProperty);
}
}
Now you can attach this property to a button in your view to define an image path. Here the attached property differs in that you define it on a different type (Button) using its owner type IconButtonProperties.
<Button ContentTemplate="{StaticResource ImageTextContentTemplate}"
local:IconButtonProperties.Source="Resources/MyImage.png"
Content="Click me!"/>
The last big difference is shown in the data template that uses the attached property with a Binding. When binding to an attached property, you have to put the property in parentheses.
<DataTemplate x:Key="ImageTextContentTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding (local:IconButtonProperties.Source), RelativeSource={RelativeSource AncestorType={x:Type Button}}}"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Margin="5, 0, 0, 0"
Text="{Binding}"/>
</Grid>
</DataTemplate>
As you can see, attached properties are invaluable for extensibility and binding in WPF. For more information on attached properties in general, you can refer to the documentation:
Attached Properties Overview
How to: Register an Attached Property
Binding Path Syntax
DependencyProperty.RegisterAttached Method
DependencyProperty.RegisterAttachedReadOnly Method
Is it not possible to have multiple layers of UserControls containing ContentControl?
I am trying to create Views for different Models that are derived, so I'd like to eliminate the need to re-create the Views for each object type, and instead provide a ContentControl, or a ContentPresenter to inject the "rest of the view". However, I can only go one level deep with this method.
Here's a simplified example.(I've removed some of the xmlns). In my case, I'm working with significantly more complex Views, and trying to eliminate duplicate code in multiple places, concerned for changes later.
I have a Base UserControl, we'll call it UserControlLevel1
<UserControl x:Class="ContentControlNesting.UserControlLevel1"
x:Name="userControlLevel1"
xmlns:local="clr-namespace:ContentControlNesting">
<StackPanel>
<TextBlock Text="UserControlLevel1ContentTop"/>
<ContentControl Content="{Binding ElementName=userControlLevel1, Path=ChildContent}"/>
<TextBlock Text="UserControlLevel2ContentBottom"/>
</StackPanel>
</UserControl>
It has the following DependencyProperty on the UserControl
namespace ContentControlNesting
{
public partial class UserControlLevel1 : UserControl
{
public UserControlLevel1()
{
InitializeComponent();
}
public static readonly DependencyProperty ChildContentProperty = DependencyProperty.Register("ChildContent", typeof(UIElement), typeof(UserControlLevel1), new PropertyMetadata(null));
public UIElement ChildContent
{
get { return (UIElement)GetValue(ChildContentProperty); }
set { SetValue(ChildContentProperty, value); }
}
}
}
The ContentControl will be used in the following UserControl called UserControlLevel2. This UserControl works fine, just the way I would expect. Or rather UserControlLevel1 works properly within UserControlLevel2.
<UserControl x:Class="ContentControlNesting.UserControlLevel2"
x:Name="userControlLevel2"
xmlns:local="clr-namespace:ContentControlNesting">
<local:UserControlLevel1>
<local:UserControlLevel1.ChildContent>
<StackPanel>
<TextBlock Text="UserControlLevel2ContentTop"/>
<ContentControl Content="{Binding ElementName=userControlLevel2, Path=ChildContent}"/>
<TextBlock Text="UserControlLevel2ContentBottom"/>
</StackPanel>
</local:UserControlLevel1.ChildContent>
</local:UserControlLevel1>
</UserControl>
Likewise, it has a single DependencyProperty for the ContentControl on this UserControl like the first. I've also tried this with differently named DependencyProperties.
namespace ContentControlNesting
{
public partial class UserControlLevel1 : UserControl
{
public UserControlLevel1()
{
InitializeComponent();
}
public static readonly DependencyProperty ChildContentProperty = DependencyProperty.Register("ChildContent", typeof(UIElement), typeof(UserControlLevel1), new PropertyMetadata(null));
public UIElement ChildContent
{
get { return (UIElement)GetValue(ChildContentProperty); }
set { SetValue(ChildContentProperty, value); }
}
}
}
Okay, so at this point, everything seems to be working fine. I've added additional content inside of the ContentControl of UserControlLevel1, and I've added another ContentControl within my UserControlLevel2 UserControl.
The problem is when I try to introduce a 3rd Tier of either UserControl or my MainWindow. Anything I add to the ContentControl of UserControlLevel2 just does not appear.
<Window x:Class="ContentControlNesting.MainWindow"
xmlns:local="clr-namespace:ContentControlNesting"
Title="MainWindow" Height="200" Width="300">
<local:UserControlLevel2>
<local:UserControlLevel2.ChildContent>
<StackPanel>
<TextBlock Text="Main Window Content Text"/>
</StackPanel>
</local:UserControlLevel2.ChildContent>
</local:UserControlLevel2>
</Window>
Am I trying to do something that's not possible? Or am I doing something wrong with ContentControl and the DependencyProperties? Should I be looking at this with a different approach?
It is possible. The system cannot resolve the ElementName in the Binding. The solution is to use the relative binding. Just replace the following line in UserControlLevel2 and your are done:
<ContentControl Content="{Binding Path=ChildContent, RelativeSource={RelativeSource AncestorType={x:Type local:UserControlLevel2}}}"/>
in my Silverlight 4 app, I try to create a simple UserControl, which will be consumed by my Application. To keep things simple, it shall have a "header" and a placeholder, where I want to place any kind of control.
<User Control ...>
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="TextBlockHeader" Text="{Binding Title}" />
<ContentPresenter x:Name="ContentPresenterObject" />
</Grid>
</UserControl>
In the code behind, I have created a property for the text of the TextBlock
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MyAccordion), null);
This way, I can set the Title property, when I use the Control in my application.
<local:MyAccordion Title="Test"/>
But it seems, that the binding at the textblock Text="{Binding Title}" doesn't make the text "Test" to be displayed as the textblocks text.
My question is: How can I make the Property Title to be displayed as the textboxes text and how do I do this for the - any type of user control containable - contencontrol?
Thanks in advance,
Frank
Maybe DataContext of control or page was not set. - First of all you should read more about a Binding ("http://www.silverlight.net/learn/data-networking/binding/data-binding-to-controls-(silverlight-quickstart)"). If you are working on real project and will design a some arhitecture, you should read about MVVM pattern.
The answer is ElementPropertyBinding. I need to reference the User Control in the Binding or add the binding in the constructor.
Create the binding in XAML:
<User Control ... x:Name="userControl">
...
<TextBlock x:Name="TextBlockHeader" Text="{Binding Title, ElementName=userControl}" />
</UserControl>
Create the binding in the constructor (Code behind)
public MyUserControl()
{
// Required to initialize variables
InitializeComponent();
TextBlockHeader.SetBinding(TextBlock.TextProperty, new System.Windows.Data.Binding() { Source = this, Path = new PropertyPath("Title") });
}
I still need to find out how to add a child control, but that's another question.
I am trying to populate a custom string array in XAML, but am receiving an error. I subclassed a ComboBox and added a string [] that I want to fill with custom values:
public class MyComboBox : ComboBox
{
public string[] MyProperty { get { return (string[])GetValue(MyPropertyProperty); } set { SetValue(MyPropertyProperty, value); } }
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(string[]), typeof(MyComboBox));
}
My XAML is as follows:
<Window x:Class="Samples.CustomArrayProperty"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Samples"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="CustomArrayProperty" Height="300" Width="300">
<Grid>
<local:MyComboBox Height="20">
<local:MyComboBox.MyProperty>
<sys:String>Monday</sys:String>
<sys:String>Wednesday</sys:String>
<sys:String>Friday</sys:String>
</local:MyComboBox.MyProperty>
</local:MyComboBox>
</Grid>
</Window>
When I run this, I get the error: "'Monday' is not a valid value for property 'MyProperty'.".
What am I doing wrong?
You can create arrays in XAML using x:Array, you still need to use it as a resource like Liz's answer.
<Window.Resources>
<x:Array Type="sys:String" x:Key="days">
<sys:String>Monday</sys:String>
<sys:String>Wednesday</sys:String>
<sys:String>Friday</sys:String>
</x:Array>
</Window.Resources>
<local:MyComboBox Height="23" MyProperty="{StaticResource days}" />
Is there any other reason you are subclassing the ComboBox beyond what you are doing here?
Because if the idea is to just provide a list of strings to the combobox then take a look at "Add collection or array to wpf resource dictionary"
As much as possible it would be better to let the combobox take care of itself - by using the ItemsSource property. So what we are doing in the linked example is providing a 'resource' containing your list of strings, and then passing this resource to the ItemsSource as follows:
<ComboBox ItemsSource="{StaticResource stringList}" />
Define a type like this:
public class StringList : List<string> { }
And then create a static resource
<Window.Resources>
<local:StringList x:Key="stringList">
<sys:String>Monday</sys:String>
<sys:String>Wednesday</sys:String>
<sys:String>Friday</sys:String>
</local:StringList >
</Window.Resources>
I hope that helps.
Edit: You could also change your DependencyProperty to use StringList instead of String[], then your dependency property will work as well.
<local:MyComboBox MyProperty="{StaticResource stringList}" Height="23" />
And then the dependencyProperty:
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty",typeof(StringList),typeof(MyComboBox),new FrameworkPropertyMetadata(null, listChangedCallBack));
static void listChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
ComboBox combo = (ComboBox)property;
combo.ItemsSource= (IEnumerable)args.NewValue;
}
Then this would effectively do the same thing as binding directly to the ItemsSource. But the main thing if I understand you correctly, is just to get the Dependency property to work.
And, an extra example with enum:
namespace MyNamespace
{
public enum OrganizationType
{
Office365,
OnPremisses
}
}
xmlns:local="clr-namespace:MyNamespace"
<x:Array Type="local:OrganizationType" x:Key="OrganizationTypeArray">
<local:OrganizationType>Office365</local:OrganizationType>
<local:OrganizationType>OnPremisses</local:OrganizationType>
</x:Array>
First Control
I can create a UserControl with a DependencyProperty called UserNameLabel. Then, I can just set the datacotext to relativesource self on the UserControl and fill the property in markup.
...
public String UserNameLabel
{
get { return (String)GetValue(UserNameLabelProperty); }
set { SetValue(UserNameLabelProperty, value); }
}
// Using a DependencyProperty as the backing store for UserNameLabel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UserNameLabelProperty =
DependencyProperty.Register("UserNameLabel", typeof(String), typeof(LoginControl), new PropertyMetadata());
<Grid x:Name="LayoutRoot">
<local:LabelTextBox Height="37" Margin="10,24,43,0" VerticalAlignment="Top" Label="{Binding UserNameLabel}"/>
</Grid>
...
Second Control
I can also create a LabelTextBox control and set the relativesource self to it with a similar Label Property.
...
public String Label
{
get { return (String)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
// Using a DependencyProperty as the backing store for Label. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(String), typeof(LabelTextBox), new PropertyMetadata(String.Empty));
...
<Grid x:Name="LayoutRoot">
<TextBlock Height="17" VerticalAlignment="Top" TextWrapping="Wrap" Text="{Binding Label}"/>
However, if I want to nest the LabelTextBox in the first usercontrol I can't seem to put a binding on the Label Property of the LabelTextBox that binds to the UserNameText Property.
It seems like a logical way to create controls where you can set the property of the parent or child control to set the child control's property.
Please help me with this.
No this is not a good approach, you should not assume you have control over any publically available property of a UserControl, that includes the DataContext.
When I want to bind a property of an element to a property of the containing UserControl I use this approach:-
<Grid x:Name="LayoutRoot">
<TextBlock Text="{Binding Parent.Label, ElementName=LayoutRoot}" />
</Grid>
This uses ElementName to set the binding source to the Child of the UserControl. The Parent in the property path then finds the UserControl itself after which we can bind to whatever property that is needed, in this case the Label property.
In this approach you need not muck about with the DataContext.