Create WPF control to displaying vector icon - c#

I'm attemping to study from Material Design for XAML source code.
Here is their GitHub:
MaterialDesignInXamlToolkit
Here is the the code I'm looking into:
PackIcon
Here is the helper class for PackIcon:
PackIconBase
PackIconDataFactory
Currently, I'm looking at their icon pack example and doing a quick test on it.
Here is the test class:
public class PackIconTest : Control
{
static PackIconTest()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(PackIconTest), new FrameworkPropertyMetadata(typeof(PackIconTest)));
}
public PackIconTest()
{
Data = "M4.93,4.93C3.12,6.74 2,9.24 2,12C2,14.76 3.12,17.26 4.93,19.07L6.34,17.66C4.89,16.22 4,14.22 4,12C4,9.79 4.89,7.78 6.34,6.34L4.93,4.93M19.07,4.93L17.66,6.34C19.11,7.78 20,9.79 20,12C20,14.22 19.11,16.22 17.66,17.66L19.07,19.07C20.88,17.26 22,14.76 22,12C22,9.24 20.88,6.74 19.07,4.93M7.76,7.76C6.67,8.85 6,10.35 6,12C6,13.65 6.67,15.15 7.76,16.24L9.17,14.83C8.45,14.11 8,13.11 8,12C8,10.89 8.45,9.89 9.17,9.17L7.76,7.76M16.24,7.76L14.83,9.17C15.55,9.89 16,10.89 16,12C16,13.11 15.55,14.11 14.83,14.83L16.24,16.24C17.33,15.15 18,13.65 18,12C18,10.35 17.33,8.85 16.24,7.76M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z";
}
private static readonly DependencyPropertyKey DataPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Data), typeof(string), typeof(PackIconTest), new PropertyMetadata(""));
public static readonly DependencyProperty DataProperty = DataPropertyKey.DependencyProperty;
[TypeConverter(typeof(GeometryConverter))]
public string Data
{
get { return (string)GetValue(DataProperty); }
private set { SetValue(DataPropertyKey, value); }
}
}
Here is the XAML usage:
<local:PackIconTest Width="200" Height="200"/>
The icon doesn't show. What am I missing?

I solved this propblem by making a style applying for PackIconTest target type.
Here is the xaml code:
<Window.Resources>
<Style TargetType="local:PackIconTest">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Path Data="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Data}"
Stroke="Black"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>

Related

How to acces to dependencyProperty within custom Canvas

i created a custom Canvas that inhirat from Canvas, i declared a new Dependency Property "NewMouseOver" that i want to affect via Setter in Trigger.
public class CanvaNetwork : Canvas
{
public CanvaNetwork() { }
public bool NewMouseOver
{
get { return (bool)GetValue(NewMouseOverProperty); }
set { SetValue(NewMouseOverProperty, value); }
}
public static readonly DependencyProperty NewMouseOverProperty =
DependencyProperty.Register("NewMouseOver", typeof(bool),
typeof(CanvaNetwork), new PropertyMetadata(false));
}
and here is my XAML :
<DataTemplate DataType="{x:Type local:Node}">
<local:CanvaNetwork x:Name="ItemCanvas_Node"
NewMouseOver="{Binding MyMouseOver}"
Background="Transparent">
<Path x:Name="Path_NodeProcess"
Stroke="Green"
Fill="Gray"
Stretch="None"
Data="{Binding Path =Geometryform}"
Visibility="{Binding Path=Visibility}">
</Path>
<local:CanvaNetwork.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="NewMouseOver" Value="True" />
</Trigger>
</local:CanvaNetwork.Triggers>
</local:CanvaNetwork>
</DataTemplate>
hera is my Node Class :
Public Node :DependencyObject
{
public static readonly DependencyProperty MyMouseOverProperty =
DependencyProperty.Register("MyMouseOver", typeof(bool), typeof(NodeProcess), new PropertyMetadata(true,new PropertyChangedCallback(On_MyMouseOver)));
private static void On_MyMouseOver(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//..some code
}
public bool MyMouseOver
{
get { return (bool)GetValue(MyMouseOverProperty); }
set { SetValue(MyMouseOverProperty, value); }
}
}
what i want is :
1-i have DependencyProperty : NewMouseOver (has get and set not like IsMouseOver in the original Canvas Class).
2-acces to NewMouseOver via Trigger/Setter and change the state of NewMouseOver .
3-via XAML : set a binding betwin : NewMouseOver (in CanvaNetwork) & MyMouseOver (in Node Class)
4-after that i'll use On_MyMouseOver (in Node Class) and MyMouseOver to make some stuff.
I think that I can answer the question about how to update the DependencyProperty in your canvas object.
To test it, I would define an "on changed" method for the dependency property. You can put a breakpoint here to verify that the dependency property is set.
class CanvaNetwork : Canvas
{
public CanvaNetwork ( ) { }
public static readonly DependencyProperty NewMouseOverProperty
= DependencyProperty.Register ( "NewMouseOver",
typeof (bool),
typeof (CanvaNetwork),
new PropertyMetadata (false, OnNewMouseOverChanged)) ;
public bool NewMouseOver
{
get { return (bool)GetValue (NewMouseOverProperty); }
set { SetValue (NewMouseOverProperty, value); }
}
public static void OnNewMouseOverChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
}
}
In the DataTemplate, you have to define the triggers within a Style.
<DataTemplate DataType="{x:Type local:Node}">
<local:CanvaNetwork x:Name="ItemCanvas_Node"
Background="red" Height="100" Width="100">
<Path x:Name="Path_NodeProcess"
Stroke="Green"
Fill="Gray"
Stretch="None"
Data="{Binding Path =Geometryform}"
Visibility="{Binding Path=Visibility}">
</Path>
<local:CanvaNetwork.Style>
<Style>
<Setter Property="local:CanvaNetwork.NewMouseOver" Value="False" />
<Style.Triggers>
<Trigger Property="Canvas.IsMouseOver" Value="True">
<Setter Property="local:CanvaNetwork.NewMouseOver" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</local:CanvaNetwork.Style>
</local:CanvaNetwork>
</DataTemplate>
You can only update a property with a trigger, if the default property is set within the style.
For this to work I have removed your attritute NewMouseOver="{Binding MyMouseOver}". From your list, points 1 and 2 work, but removing this attribute means that point 3 does not work.
However, I think that you are probably taking the wrong approach anyway. Wouldn't it be better to hook up the MouseOver event to a command property in your Node class, as described here:
How to make MouseOver event in MVVM?

Custom Control x:class Problems and Animation

I am trying to create a Menu Custom Control that contains some Animations to make things look a little more "Fluid". This is where I am running into Problems, I would like to have the Width of my Custom Control be driven by a animation to emulate the expanding of the menu. I can achieve this by using Triggers in the Template but using this method I can't seem to find a way to bind the "to", I had read that this isn't allowed because of Thread Safety so I figured I would just use the "Click" event and do the Animation in the Code Behind.
Well when I tried to add my class to the Resource Dictionary I end up getting a bunch of errors saying '"DefaultStyleKeyProperty" does not exist in the current context' in the Class file. I need to add my class to the Resource Dictionary in order to be able to get to any of the UI Objects. So I am a little stuck on how to achieve this, or am I going about this the wrong way?
Here is what I have so far:
NavMenu.xaml
<ResourceDictionary x:Class="Happ.UI.Controls.NavMenu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Happ.UI.Controls">
<Style TargetType="{x:Type local:NavMenu}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Background="Yellow" Orientation="Vertical"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NavMenu}">
<Border Background="{TemplateBinding Background}" BorderBrush="Transparent">
<ToggleButton x:Name="btnMenu"
Margin="10" Padding="4" Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Top"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=IsExpanded}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
NavMenu.cs
namespace Happ.UI.Controls {
public partial class NavMenu : Menu {
static NavMenu() {
DefaultStyleKeyProperty.OverrideMetadata( typeof( NavMenu ), new FrameworkPropertyMetadata( typeof( NavMenu ) ) );
}
public bool IsExpanded {
get {
return (bool)GetValue( IsExpandedProperty );
}
set {
TimeSpan tsTime = new TimeSpan( 0, 0, 0, 0, 500 );
DoubleAnimation mnuAnim = new DoubleAnimation( MinWidth, tsTime );
btnMenu.BeginAnimation( Width, mnuAnim );
SetValue( IsExpandedProperty, value );
}
}
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register( "IsExpanded", typeof( bool ), typeof( NavMenu ), new PropertyMetadata( true ) );
}
}
}
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:Happ.UI.Controls">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Happ.UI.Controls;component/Templates/NavMenu.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
I tried adding the xClass into the Generic.xaml also with no luck, when I did that I received another additional error: "Partial declarations of 'NavMenu' must no specify different base classes".
Edit
I knew there had to be a better way of doing this, using the Interactivity Library. What I ended up doing is creating a new class specifically for the Animations. Then I can attach the new TriggerAction to my Control, one thing that I did figure out is I need to have a ListContainer for the target animation element. This is because there is no way to achieve the animation from 0 to "Auto", the Panel provides a way to get the children and calculate the "Auto" width in code. Here are my updates:
NavMenu.cs
namespace Happ.UI.Controls {
public class NavMenu : Menu {
static NavMenu() {
DefaultStyleKeyProperty.OverrideMetadata( typeof( NavMenu ), new FrameworkPropertyMetadata( typeof( NavMenu ) ) );
}
/// <summary>
/// Flag to Tell if the Menu is Expanded
/// </summary>
public bool IsExpanded {
get {
return (bool)GetValue( IsExpandedProperty );
}
set {
SetValue( IsExpandedProperty, value );
}
}
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register( "IsExpanded", typeof( bool ), typeof( NavMenu ), new PropertyMetadata( true ) );
}
}
NavMenu.Animations.cs
namespace Happ.UI.Controls {
public class NavMenuAnimations : System.Windows.Interactivity.TriggerAction<UIElement> {
/// <summary>
/// Sets the Target Element for the Animation
/// </summary>
public Panel TargetPanel {
get {
return (Panel)GetValue( TargetPanelProperty );
}
set {
SetValue( TargetPanelProperty, value );
}
}
public static readonly DependencyProperty TargetPanelProperty =
DependencyProperty.Register( "TargetPanel", typeof( Panel ), typeof( NavMenuAnimations ),
new PropertyMetadata( null ) );
/// <summary>
/// Sets the Target Element for the Animation
/// </summary>
public double Seconds {
get {
return (double)GetValue( SecondsProperty );
}
set {
SetValue( SecondsProperty, value );
}
}
public static readonly DependencyProperty SecondsProperty =
DependencyProperty.Register( "Seconds", typeof( double ), typeof( NavMenuAnimations ),
new PropertyMetadata( (double)0.5 ) );
/// <summary>
/// This is the Main Animation Method that is called each time the Trigger occurs
/// </summary>
/// <param name="parameter"></param>
protected override void Invoke( object parameter ) {
double maxWidth = 0;
//Make sure that we have a Target Element
if( TargetPanel == null ) {
throw new Exception( "No Target Element specified for the animation. (NavMenu Animations)" );
}
//Make sure the Min Width and Height are Set
TargetPanel.MinWidth = ( Width > 0 ) ? Width : TargetPanel.MinWidth;
//Check if the MaxWidth has been set
if( Double.IsInfinity( TargetPanel.MaxWidth ) ) {
//Loop through the Children and Get Width
foreach( UIElement elem in TargetPanel.Children ) {
//Update the Max Width of the Panel
maxWidth += elem.RenderSize.Width;
}
//Check if we found a MaxWidth. if not return
if( maxWidth == 0 )
return;
//Assign the new MaxWidth
TargetPanel.MaxWidth = maxWidth;
}
//Check if Width is at Minumum (Shrunk)
if( TargetPanel.Width == TargetPanel.MinWidth ) {
TargetPanel.BeginAnimation( FrameworkElement.WidthProperty, new DoubleAnimation( TargetPanel.MaxWidth, TimeSpan.FromSeconds( Seconds ) ) );
}
else {
TargetPanel.BeginAnimation( FrameworkElement.WidthProperty, new DoubleAnimation( TargetPanel.MinWidth, TimeSpan.FromSeconds( Seconds ) ) );
}
}
}
}
NavMenuTemplate.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:Happ.UI.Controls">
<Style TargetType="{x:Type local:NavMenu}">
<Setter Property="Width" Value="50"/>
<Setter Property="Padding" Value="10" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel x:Name="navMenu" Orientation="Vertical">
</StackPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NavMenu}">
<Border Background="{TemplateBinding Background}"
BorderBrush="Transparent"
Padding="{TemplateBinding Padding}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Viewbox Grid.Row="0"
HorizontalAlignment="Left" VerticalAlignment="Top"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=IconSize}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=IconSize}">
<ToggleButton x:Name="btnMenu" Grid.Row="0"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=MenuIcon}"
Background="Transparent"
Padding="0"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:NavMenu}}, Path=IsExpanded}">
</ToggleButton>
</Viewbox>
<ItemsPresenter Grid.Row="1" MinWidth="50">
<i:Interaction.Triggers>
<i:EventTrigger SourceName="btnMenu" EventName="Click" >
<local:NavMenuAnimations TargetPanel="{Binding ElementName=navMenu}" Technique="ExpandWidth" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ItemsPresenter>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
This is getting very close to what I need, I only have one problem remaining. Because of the orientation of my Elements I needed to define a ItemsPanelTemplate which is the Element that I need to Animate. The problem is however I can not figure out how to locate the "navMenu" element. I know this is simply a Context issue I just don't know how to get to the right place. I have tried using the RelativeParent={RelativeParent TemplatedParent} but I can not use the assigned name with it. Can anyone tell me how I can pass a reference to the ItemsPanelTemplate?
Why is the NavMenu defined as partial in the first place? This should only be class wihout any XAML except for the default template that you defined in your NavMenu.xaml file. You should not have any NavMenu.xaml.cs file.
Also your IsExpanded CLR wrapper should only set the value of the dependency property.
To get a reference to the "btnMenu" ToggleButton that is defined in the template of the custom control you should override the OnApplyTemplate method:
public class NavMenu : Menu
{
static NavMenu()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NavMenu), new FrameworkPropertyMetadata(typeof(NavMenu)));
}
public bool IsExpanded
{
get
{
return (bool)GetValue(IsExpandedProperty);
}
set
{
SetValue(IsExpandedProperty, value);
}
}
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register("IsExpanded", typeof(bool), typeof(NavMenu), new PropertyMetadata(true));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
System.Windows.Controls.Primitives.ToggleButton btnMenu = Template.FindName("btnMenu", this) as System.Windows.Controls.Primitives.ToggleButton;
TimeSpan tsTime = new TimeSpan(0, 0, 0, 0, 500);
DoubleAnimation mnuAnim = new DoubleAnimation(0.0, MinWidth, tsTime);
btnMenu.BeginAnimation(WidthProperty, mnuAnim);
}
}

DependencyProperty compiles and works but Intellisense doesn't think so

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.

Reuseable CustomControl Templates

I have a problem with reusable controls, and I nedd your help. The problem looks like this, I have created a custom control:
public class ControlExtender : ContentControl
{
static ControlExtender()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ControlExtender), new FrameworkPropertyMetadata(typeof(ControlExtender)));
}
public override void OnApplyTemplate()
{
}
}
In addition I have a ControlTemplate
<Style TargetType="{x:Type controls:ControlExtender}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
...content....
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I created an additional class, which contains a handful of AttachedProperties. Among other things, this one:
public class Extender
{
public static readonly DependencyProperty ControlTemplateProperty = DependencyProperty.RegisterAttached(
"ControlTemplate",
typeof(ControlTemplate),
typeof(Extender),
new FrameworkPropertyMetadata(
null,
MetadataOptions,
OnControlTemplateChanged,
CoerceRadingControlTemplate));
public static ControlTemplate GetControlTemplate(UIElement element)
{
return (ControlTemplate)element.GetValue(ControlTemplateProperty);
}
public static void SetControlTemplate(UIElement element, ControlTemplate value)
{
element.SetValue(ControlTemplateProperty, value);
}
This Extender class creates a new popup. Content of the popup should be the custom control ControlExtender. In my XAML code I want to implement a statement like this:
p: Extender.ControlTemplate = "{?}"
But how can I specify the custom control ControlExtender here? Sorry if this question is too trivial, but now I'm stuck.
It is fairly simple
since you've defined the target type it also act as the key for the resource
p:Extender.ControlTemplate = "{Binding Setters[0].Value, Source={StaticResource {x:Type controls:ControlExtender}}}"
this will effectively look for the template if there is some issue like resolving at compile time you could use DynamicResource too
p:Extender.ControlTemplate = "{Binding Setters[0].Value, Source={DynamicResource {x:Type controls:ControlExtender}}}"
that's all to retrieve the defined template, the trick here is to get the first setter's value which is the control template

Failing to get/set custom attached property

I'm trying to create a "DropDownButton" with an icon in it, and I want to be able to set the icon source via an attached property (I found this is the (only?) way to do this). But for some reason, everything I tried fails, the best I could get was an empty Image container.
I thought this looked pretty good, but now I'm getting these errors:
The local property "Image" can only be applied to types that are derived from "IconButton".
The attachable property 'Image' was not found in type 'IconButton'.
The attached property 'IconButton.Image' is not defined on 'Button' or one of its base classes.
I'm probably doing this completely wrong (I've been trying and editing for about 2 hours now), but I just know there must be a way of doing this.
Relevant code is provided below, if anybody can even point me in the right direction that would be awesome!
EDIT: Updated code, still experiencing issue
Now I get this error in debug log:
System.Windows.Data Error: 40 : BindingExpression path error: 'Image' property not found on 'object' ''ContentPresenter' (Name='')'. BindingExpression:Path=Image; DataItem='ContentPresenter' (Name=''); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')
ImageButton.cs (thank you Viv):
class ImageButton : Button
{
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender));
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
}
ImageButton Style:
<Style TargetType="{x:Type Controls:ImageButton}" x:Key="FormIconDropDownButton">
<Setter Property="Margin" Value="5" />
<Setter Property="Padding" Value="10,5,4,5" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type Controls:ImageButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Style="{StaticResource FormButtonIcon-Small}"
Source="{Binding Image, RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBlock Grid.Column="1"
Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
Margin="0,0,9,0"/>
<Path Grid.Column="2"
Fill="Black"
Data="M 0 0 L 3.5 4 L 7 0 Z"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
In window xaml:
<Controls:ImageButton Content="Hello"
Style="{StaticResource FormIconDropDownButton}"
Image="{StaticResource Icon-Small-Locations}" />
You're just using the wrong control type in your xaml. You're still using the base class instead of your derived class.
Also you're declaring a dependency property not an attached property.
Attached Properties are registered with DependencyProperty.RegisterAttached(...)
Now you'll need to add a namespace to where your IconButton class is defined in your xaml such as
xmlns:local="clr-namespace:Mynamespace"
and then switch occurrences of
{x:Type Button} to {x:Type local:IconButton}
and
<Button ...> to <local:IconButton ...>
I wouldn't recommend an attached property for this tbh. Attached properties get way over-used when they shouldn't be probably just my opinion.
Check This thread for some differences between DP and AP usage. In this case it's a custom Button that shows an Image. Make it unique than homogenize the lot.
Update:
Download link using derived class(ImageButton) of Button with normal DP.
Everything looks correct except that you haven't declared an attached property. Instead you've just declared a normal DependencyProperty on your IconButton class which is then only valid to set on IconButton or classes derived from it. The declaration of an attached property (which can be set on any type) uses a different call to register and also uses get/set methods instead of a wrapper property:
public static readonly DependencyProperty ImageProperty =
DependencyProperty.RegisterAttached(
"Image",
typeof(ImageSource),
typeof(IconButton),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender));
public static ImageSource GetImage(DependencyObject target)
{
return (ImageSource)target.GetValue(ImageProperty);
}
public static void SetImage(DependencyObject target, ImageSource value)
{
target.SetValue(ImageProperty, value);
}
This is my example of Extending Base class, use Dependency Properties in Style and in View.
For more details write in this post.
public class ItemsList : ListView {
public static readonly DependencyProperty ItemIconProperty = DependencyProperty.Register("ItemIcon", typeof(ImageSource), typeof(ItemsList));
public ImageSource ItemIcon {
get { return (ImageSource)GetValue(ItemIconProperty); }
set { SetValue(ItemIconProperty, value); }
}
public static readonly DependencyProperty DoubleClickCommandProperty = DependencyProperty.Register("DoubleClickCommand", typeof(ICommand), typeof(ItemsList));
public ControlTemplate DoubleClickCommand {
get { return (ControlTemplate)GetValue(DoubleClickCommandProperty); }
set { SetValue(DoubleClickCommandProperty, value); }
}
}
/Style for Extended ItemList where is 'ItemIcon' DependencyProperty Declared/
<Style x:Key="BaseDataSourcesWindowListMenuStyle" TargetType="Controls:ItemsList">
<Setter Property="ItemIcon" Value="/Presentation.Shared;component/Resources/Images/data_yellow.png" />
</Style>
<Style x:Key="DataSourcesListMenuStyle" TargetType="Controls:ItemsList"
BasedOn="{StaticResource BaseDataSourcesWindowListMenuStyle}">
<Setter Property="DoubleClickCommand" Value="{Binding Path=VmCommands.EditDataSourceDBCommand}" />
</Style>
/HOW I'M USING 'ItemIcon' DependencyProperty ON VIEW/
<Controls:ItemsList Grid.Column="0" Grid.Row="1" Margin="8" ItemsSource="{Binding DataSourceDbs}"
Style="{DynamicResource DataSourcesListMenuStyle}"
SelectedItem="{Binding SelectedDataSourceDB, Mode=TwoWay}" />
First of all, I think that's a dependency property you declared there, and it belongs to the IconButton custom control, and you're trying to use it on a Button control.
See http://msdn.microsoft.com/en-us/library/ms749011.aspx for reference on how to declare attached properties, and assign that property to the Button class instead of the IconButton since you're not using that custom control in the code above.

Categories

Resources