Attached Properties - wpf

I am a little confused about WPF attached properties. When you use an attached property that attached property can only be read and used by the class that defines it correct? For example if I wanted to use some attached property as a hover color on a button, can I get the attached property value from the button's template, and will I be able access the attached property from the button to set the hoover color?

Adding to the answer from H.B. using an example:
For example if I wanted to use some attached property as a hover
color on a button, can I get the attached property value from the
button's template, and will I be able access the attached property
from the button to set the hover color?
Yes, you sure can. Say that you have an Attached Property called HoverBrush defined in a class called SomeClass, you can set the value on the instance and bind to it in the template
<StackPanel>
<StackPanel.Resources>
<ControlTemplate x:Key="MyButtonTemplate" TargetType="{x:Type Button}">
<Border x:Name="border" Background="Gray">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border"
Property="Background"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=(local:SomeClass.HoverBrush)}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</StackPanel.Resources>
<Button Content="Blue Hover"
local:SomeClass.HoverBrush="Blue"
Template="{StaticResource MyButtonTemplate}"/>
<Button Content="Green Hover"
local:SomeClass.HoverBrush="Green"
Template="{StaticResource MyButtonTemplate}"/>
</StackPanel>
The attached property in question is defined like this
public class SomeClass
{
public static DependencyProperty HoverBrushProperty =
DependencyProperty.RegisterAttached("HoverBrush",
typeof(Brush),
typeof(SomeClass),
new PropertyMetadata(null));
public static void SetHoverBrush(DependencyObject obj, Brush value)
{
obj.SetValue(HoverBrushProperty, value);
}
public static Brush GetHoverBrush(DependencyObject obj)
{
return (Brush)obj.GetValue(HoverBrushProperty);
}
}

Have you read the overview? If not, do it.
Attached properties, like dependency properties, just register another key that can be used in the properties dictionary of controls. You can set values anywhere and you can retrieve them anywhere, they are not restricted by type. This means that you may only want it to be set on Buttons but it can be set on TextBoxes too.
Every control has its own dictionary of property keys and values, an attached property allows you to write a value to those dictionaries using a new key. As those dictionaries are independent they can have separate values for the same property which is set and accessed via the static field property declaration.
As those properties are attached you will have to get the values via the GetValue (as the classes cannot provide a CLR-wrapper themselves).

Related

Relative Path not resolved in Template Binding WPF

I am trying to create an Image Button in WPF. What I have done is
Create a user control inherited from Button and declare a dependency property in it which supposed to have the required image.
In resource dictionary declare the xaml template for it.
Pass relative and full path to that dependency property, full path works but relative not.
User Control Class
public class ButtonWithImage : Button
{
public ImageSource ButtonImage
{
get { return (ImageSource)GetValue(ButtonImageProperty); }
set { SetValue(ButtonImageProperty, value); }
}
// Using a DependencyProperty as the backing store for ButtonImage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonImageProperty =
DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(ButtonWithImage));
}
Resource Dictionary Code
<Style x:Key="ButtonWithImageStyle" TargetType="complaintRegister:ButtonWithImage">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="complaintRegister:ButtonWithImage">
<StackPanel>
<Image Source="{Binding ButtonImage, RelativeSource={RelativeSource TemplatedParent}}" Width="50" Height="50"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
XAML
<complaintRegister:ButtonWithImage x:Name="ButtonAdd" Content="Add New Complaint" Style="{StaticResource ButtonWithImageStyle}"
ButtonImage="Resources\Plus.png"
Width="150" Height="75" Margin="10 0" Command="{Binding NewCommand}">
</complaintRegister:ButtonWithImage>
Error
It does displays the image in design mode, but at runtime throws this exception
Cannot locate resource 'resources/plus.png'
I can't help but thinking it is trying to resolve 'resources/plus.png' instead of 'Resources/Plus.png.
I bet there is a binding error displayed in output window. :)
How about you post us that error message here.
Btw, in such cases its more propriate to use TemplateBinding instead of RelativeSource TemplatedParent.
TemplateBinding is lets say it like this, specified to work well in control's template. :)
Check those links out:
http://msdn.microsoft.com/en-us/library/ms742882(v=vs.110).aspx
http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
Edit :
Ahhh you are talking about location of your image.
By default, the Image control only recognize compiled resource image when using XAML to specify its Source property. But, we can use converter or custom markup extension to achieve this goal. The following links contain information about data binding converter and markup extension.
Therefore set that png to build action -> resource and rebuild your solution or use this:
<Image>
<Image.Source>
<BitmapImage UriSource="../Relative/Path/To/Image.png" />
</Image.Source>
</Image>
Though if you wish to use MVVM on this and change path based on data I suggest you to use Bindings:
<Image Source="{Binding MyPath}" Height="50"... />

A bit unsual way to change look of a custom ContentControl

I might be just missing something or probably today is not my day but what I am trying to do keeps failing.
I have a custom control called MyContentControl. It looks like this:
public class MyContentControl : ContentControl
{
static MyContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyContentControl), new FrameworkPropertyMetadata(typeof(MyContentControl)));
}
public DockPanel DifferentLook
{
get;
set;
}
public string Txt
{
get;
set;
}
protected override void OnInitialized(EventArgs e)
{
if (this.DifferentLook != null)
{
this.Content = this.DifferentLook;
}
Binding b = new Binding("Txt");
b.Source = this;
this.SetBinding(ContentProperty, b);
base.OnInitialized(e);
}
}
This is its theme:
<Style TargetType="{x:Type local:MyContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyContentControl}">
<Border Background="{TemplateBinding Background}" Margin="{TemplateBinding Padding}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is MainWindow:
<local:MyContentControl Txt="texty text">
<local:MyContentControl.DifferentLook>
<DockPanel>
<TextBlock Text="Content = " DockPanel.Dock="Left"/>
<ContentPresenter DockPanel.Dock="Left"/>
</DockPanel>
</local:MyContentControl.DifferentLook>
</local:MyContentControl>
When no "DifferentLook" specified I would like the control to use the ContentPresenter defined inside the default ControlTemplate.
If I have DifferentLook set then it should display the control in different look.
See the method OnInitalized.
Now the problem is when I apply DifferentLook it seems that the DifferentLook.ContentPresenter is not working.
Why is DifferentLook.ContentPresenter not appyling the content correctly?
The output on window is "texty text" but it should be "content = texty text".
EDIT: This is the light version. I created this and kept things as simple as possible to demostrate the problem. In real the custom control is a bit huge and user may not override ControlTemlates.
Do you guys have an idea how to solve this with the given requirements?
Buddy I am not quite agreed with the approach you are taking here. But I can point out what you are doing wrong and why you are not able to see your DockPanel.
Since you have defined the ControlTemplate for your ContentControl having ContentPresenter, then any Content you set on your ContentControl will be placed inside the ContentPresenter of your ControlTemplate defined in style whether it is the Content set by Binding or Directly placed inside on control (like DockPanel here)
In your example, OnInitialized() function first set the Content to the DockPanel and then immediatly set Binding on Content. The moment Binding is set it will override the previously set DockPanel with the Source value which is string Txt(texty text).
So if you remove the Binding setting from Oninitialized you will see your DockPanel. But wait there is one more thing. Inside the Content of your ContentPresenter with Content bound as Content="{Binding}" which will make your programme to go into infinite recursion as it will try to bind it to self and on and on. So you will have to set the Content Binding of inner ContentPresenter to some property of Parent ContentControl Context. So just for testing try removing the content binding from inner contentpresenter and give some static value.

Silverlight GetTemplateChild of control within control returns null

I'm trying to add an indicator (I'm using TextBlock) to the datepicker control.
Visually it works but I can't get the control via GetTemplateChild. I assume it's something to do with the fact that the TextBlock control I added is in the DatePickerTextBox style template as opposed to the DatePicker style template.
I've tried DefaultStyleKey (although I don't think this makes sense as it's the TextBox control within DatePicker that's the problem) and using OnApplyTemplate and UpdateLayout on the TextBox control.
Here's a snippet of the Dictionary.xaml
<Style x:Key="Ind_DatePickerTextBoxStyle" TargetType="primitives:DatePickerTextBox">
...
<Grid VerticalAlignment="Stretch">
<TextBlock x:Name="Indicator" Text="*" Style="{StaticResource IndicatorStyle}" Visibility="Collapsed"/>
...
<!--datepicker style snippet-->
<primitives:BF_DatePickerTextBox
x:Name="TextBox"
SelectionBackground="{TemplateBinding SelectionBackground}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
Grid.Column="0"
Style="{StaticResource Ind_DatePickerTextBoxStyle}" />
GetTemplateChild can only be used from "your control" to get a control defined in it's [control]template. When you have defined a control and given it a style you can use GetTemplateChild
public class MyCustomControl : Control
{
override OnApplyTemplate()
{
var textbox = GetTemplateChild("TextBox");
}
}
<Style TargetType="local:MyCustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyCustomControl">
<TextBox x:Name="TextBox"/>
</ControlTemplate>
</Setter.Value>
</Setter>
So in this example I was able to use GetTemplateChild to get the TextBox child inside the control because I was accesings my [control]template. I cannot use GetTemplateChild to get the TextBox from another control that uses MyCustomControl. Only MyCustomControl can use GetTemplateChild to get the TextBox.
Now I can extend MyCustomControl to do the same
public class MyOtherCustomControl : MyCustomControl
{
override OnApplyTemplate()
{
var textbox = GetTemplateChild("TextBox");
}
}
I hope this helps!

WPF: How to properly override the methods when creating custom control

I am creating a custom control Toolbox that is derived from ItemsControl. This toolbox is supposed to be filled with icons coming from the database. The definition looks like this:
public class Toolbox : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ToolboxItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ToolboxItem);
}
}
Toolboxitem is derived from ContentControl.
public class ToolboxItem : ContentControl
{
static ToolboxItem()
{
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(ToolboxItem), new FrameworkPropertyMetadata(typeof(ToolboxItem)));
}
}
Since the number of icons stored in a database is not known I want to use the data template:
<DataTemplate x:Key="ToolBoxTemplate">
<StackPanel>
<Image Source="{Binding Path=url}" />
</StackPanel>
</DataTemplate>
Then I want the Toolbox to use the template.
<Toolbox x:Name="NewLibrary" ItemsSource="{Binding}" ItemTemplate="ToolBoxtemplate">
</Toolbox>
I'm using ADO.NET entity framework to connect to a database. The code behind:
SystemicsAnalystDBEntities db = new SystemicsAnalystDBEntities();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
NewLibrary.ItemsSource = from c in db.Components select c;
}
However, there is a problem. When the code is executed, it displays the object from the database (as the ItemSource property is set to the object from the database) and not the images. It does not use the template. When I use the static images source it works in the right way
I found out that I need to override the PrepareContainerForItemOverride method.But I don't know how to add the template to it.
Thanks a lot for any comments.
Additional Information
Here is the ControlTemplate for ToolboxItem:
<ControlTemplate TargetType="{x:Type s:ToolboxItem}">
<Grid>
<Rectangle Name="Border"
StrokeThickness="1"
StrokeDashArray="2"
Fill="Transparent"
SnapsToDevicePixels="true" />
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="true">
<Setter TargetName="Border"
Property="Stroke"
Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
ToolboxItem is overriding the default style for ContentControl. You haven't posted the overridding style (from generic.xaml), but I suspect your problem is with the template defined in that style. Your ToolboxItem template needs to contain a ContentPresenter, e.g.:
<Style TargetType="{x:Type local:ToolboxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ToolboxItem}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Alternatively, if you don't need to do anything special in the ToolboxItem UI, just remove the DefaultStyleKeyProperty.OverrideMetadata call.
Note that you do not need to override PrepareItemForContainerOverride.
You have correctly implemented the methods. The problem is, as I suspected, in your ToolBoxItem ControlTemplate which you posted recently. If it had used an ordinary <ContentPresenter /> you would have been fine. You ran into ContentPresenter's "magic" properties which are only set automatically if you don't set any of them.
Here is the problem code in your ControlTemplate:
<ContentPresenter
Content="{TemplateBinding ContentControl.Content}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
The problem is that you are setting the Content property but not setting the ContentTemplate property. ContentPresenter has custom code that automatically creates bindings for its Content, ContentTemplate, and ContentTemplateSelector properties, but only if the Content property is not set manually.
In your case, the Content property is being set manually, so the automatic mechanism is disabled and so ContentTemplate is null.
Although it would be possible to manually set all three automatic properties like this:
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
Your best choice is to omit them entirely and just accept ContentPresenter's default behavior, like this:
<ContentPresenter
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
Note: NET Framework 3.5 adds a fouth automatically-bound property, ContentStringFormat, which would be missed if you manually bound the three properties instead of letting ContentPresenter do it for you automatically.
Is this code directly copied from your project? If so, the following
ItemTemplate="ToolBoxtemplate"
should be:
ItemTemplate="{StaticResource ToolBoxTemplate}"
I'm not entirely sure, but you might need to set the ContentTemplate of your Toolbox container explicitly in PrepareContainerForItemOverride, since you may have overridden the behavior that automatically sets the template. You may need to set it as a Binding, as I'm not sure if the containers are re-generated if the ItemTemplate changes.
It looks like your problem could be that your url property is not exposed within ToolBoxItem. When your items are bound directly to ToolBox the url property is directly exposed to the DataTemplate.
In order for your example to work, ToolBoxItem would need to have:
public ImageTypeHere url { get; private set; }
If this is really a simple implementation it would probably benefit you more to use (or at least derive from) a ListBox and use a custom DataTemplate and Style for your ListBoxItems rather than creating your own control.

Silverlight: TemplateBinding a Rectangle

I'm having a heck of a time trying to template bind the StrokeThickness of a rectangle.
My goal is to allow a user of my custom control to set a property called SelectedBorderThickness which will, in fact, set the StrokeThickness of a rectangle.
I thought I understood templating but I guess I really don't.
If I do this:
<Rectangle x:Name="myRect" Height="100" Width="100" Stroke="Black" SelectedBorderThickness="5" />
Can someone please show me how to write the Style elements to get this to work?
You should add more details to the question and people will be able to help you more easily. I think I have figured out what you want though.
You are looking to make a custom templated silverlight control, containing a bunch of elements incluiding a rectangle in its template. You would like a user to be able to set the thickness of that rectangle inside the control with a property on the control itself. From what you put above, I don't know how much you have written in your code -- so I will just post a nearly complete example of what you are after.
First I created a templated custom control in visual studio, and added the new dependancy property we want a user to be able to set:
public class TestControl : Control
{
static public DependencyProperty SBTProperty { get; set; }
static TestControl()
{
SBTProperty = DependencyProperty.Register("SelectedBorderThickness", typeof(double), typeof(TestControl),null);
}
public TestControl()
{
this.DefaultStyleKey = typeof(TestControl);
}
public double SelectedBorderThickness
{
get { return (double)GetValue(SBTProperty); }
set { SetValue(SBTProperty, value); }
}
}
Then I set up the template in Generic.xaml (for my example the only thing I have in my control is the rectangle since I don't know what you want in there):
<Style TargetType="local:TestControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestControl">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Rectangle Fill="Bisque" Stroke="Black" StrokeThickness="{TemplateBinding SelectedBorderThickness}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now I am all set up to use it from xaml in other parts of my application. For my example, I put one right in the center of my MainPage:
<local:TestControl SelectedBorderThickness="75"></local:TestControl>
EDIT:
After reading your code below, I see now what the problem is. You're trying to do a template binding, but the way you have it it's going to try to bind to the current template, which is the template for listboxitem and not your custom listbox. What you really want in this situation is to do a RelativeBinding with FindAncestor to jump up the tree to the template of your custom listbox, but MS hasn't yet implemented that kind of binding in Silverlight (even though it's pretty common in WPF). Luckily in your specific situation we can finagle the right object through the path in a TemplatedParent binding, without having to write a bunch of messy codebehind to emulate an ancestor binding:
StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Parent.SelectedBorderThickness}"
Dump that into the Rectangle in your template posted above and it should work -- it will access the content of the ListBoxItem (which is whatever you are displaying), and then access that objects Parent (which will be your custom listbox). From there we just hit up the property we set up before.
If you want a cleaner solution, join the chorus of us asking MS to implement ancestor binding in Silverlight.

			
				
Here's the problem section, it's when I'm attempting to style the ItemContainerStyle for my custom control which derives from a ListBox:
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<!-- VSM stuff removed for clarity -->
<ContentPresenter
x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
<Rectangle x:Name="FocusVisualElement"
Stroke="Goldenrod"
StrokeThickness="{TemplateBinding SelectedBorderThickness}"
Visibility="Collapsed"
RadiusX="1"
RadiusY="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
The problems is that when I set StrokeThickness = {TemplateBinding SelectedBorderThickness} on the Rectangle and then try to use the control in a test app, I get a ParserError:
Message: Unknown attribute StrokeThickness on element Rectangle
If I hardcode the StrokeThickness to 3 (or whatever), it parses fine and I can view the test app.
In the end, all I'm really trying to do is create a property that shows up in Intellisense so that my (eventual) end users of my custom control can change the color and border thickness, radius, etc. of the highlight on a hovered and selected ListBoxItem in a dynamically bound custom ListBox. It shouldn't be this dang hard.
The dang comments are too restricted. I'm not trying to answer my own question (I wish I could).
David, your code works fine when you add ListBoxItems statically. When adding them dynamically, the thickness doesn't change. To test this out, I added a new TestControl in MainPage:
<StackPanel>
<local:TestControl SelectedBorderThickness="9" x:Name="h1n1">
<TextBlock Text="Honk1"></TextBlock>
<TextBlock Text="Honk2"/>
</local:TestControl>
<local:TestControl x:Name="SwineFlu" SelectedBorderThickness="20" />
</StackPanel>
In the code-behind I added:
ObservableCollection<string> test = new ObservableCollection<string>();
test.Add("Hi David");
test.Add("Hello World");
SwineFlu.ItemsSource = test;

Resources