Control as dependencyproperty / WPF - c#

I have my custom control derived from Control class. I want to create dependency property of another control (for example, button) and place it in ControlTemplate (so button can be placed in xaml and MyControl's users can subscribe to it's events etc.). May someone tell me, how can I do it?
Here is result code example:
public class MyControl: Control
{
static MyControl( )
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
public static readonly DependencyProperty MyButtonProperty =
DependencyProperty.Register("MyButton",
typeof(Button),
typeof(MyControl),
new PropertyMetadata(default(Button)));
public Button MyButton
{
get
{
return (Button) GetValue(MyButtonProperty);
}
set
{
SetValue(MyButtonProperty, value);
}
}
}
xaml:
<ControlTemplate TargetType="{x:Type lib:MyControl}">
<Canvas>
<Border Child="{TemplateBinding MyButton}">
</Border>
</Canvas>
</ControlTemplate>

Your control's template can declare a dependency on child controls via the TemplatePartAttribute. You then get an instance of that dependency in your OnApplyTemplate method.
[TemplatePart(Name = PartButton, Type = typeof(ButtonBase))]
public class MyControl : Control
{
private const string PartButton = "PART_Button";
private ButtonBase buttonPart;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.buttonPart = GetTemplateChild(PartButton) as ButtonBase;
}
}
Your control template would then look something like:
<Style TargetType="MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="MyControl">
<Border ...>
<Button x:Name="PART_Button" .../>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style
Note that this.buttonPart could be null if the template did not include an appropriately named ButtonBase within it. You should strive to ensure your control still works when template parts are missing.

Related

How to change the roundness of a button in a generic way

I am currently working on making my WPF application a little bit more generic.
Up to this point, for each button I wanted to create, I used a different style to modify roundness (and it creates a lot of useless code).
Using the following code I've managed to create a variable I can change from the XAML file, but I cannot link it to the roundness itself.
Could anyone please tell me what I am doing wrong? I already have checked on so many forums but no one seems to have the answer other than "don't do it in a generical way".
I can precise that everything is compiling and the style is otherwise correctly applied to the button (there is no xaml linking problem).
The style I am using:
<Style x:Key="AwakeButton" TargetType="{x:Type customcontrols:AwakeButton}" BasedOn="{StaticResource {x:Type Button}}"
xmlns:extensions="Awake.Services.Properties:Extensions">
<Setter Property="customcontrols:AwakeButton.BorderRoundness" Value="4.0"/>
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="{Binding Path=BorderRoundness}" />
<!--<Setter Property="CornerRadius" Value="10" />-->
</Style>
</Style.Resources>
</Style>
The overload of the button I created to do so:
public class AwakeButton : Button
{
public AwakeButton()
{
}
public static DependencyProperty BorderRoundnessProperty =
DependencyProperty.RegisterAttached("BorderRoundness", typeof(double), typeof(AwakeButton));
public static void SetBorderRoundness(UIElement element, double value)
{
element.SetValue(BorderRoundnessProperty, value);
}
public static double GetBorderRoundness(UIElement element)
{
return (double)element.GetValue(BorderRoundnessProperty);
}
}
How I am using it in the page:
<customcontrols:AwakeButton Style="{StaticResource AwakeButton}" Margin="142,115,0,0" Width="136" Height="167" BorderRoundness="5">
You have to bind the BorderRoundness to the parent AwakeButton, otherwise it is resolved using the current DataContext, which does not contain this property. Furthermore, if you derive from Button, you do not have to make the dependency property attached, you could just register a normal one using the Register(...) method. Also make DPs static and readonly.
<Setter Property="CornerRadius" Value="{Binding BorderRoundness, RelativeSource={RelativeSource AncestorType={x:Type local:AwakeButton}}}" />
If you do not change anything special about the button, you could also create attached properties instead of a dedicated sub type just for exposing a BorderRoundness property.
public static class ButtonProperties
{
public static readonly DependencyProperty BorderRoundnessProperty =
DependencyProperty.RegisterAttached("BorderRoundness", typeof(double), typeof(ButtonProperties));
public static void SetBorderRoundness(UIElement element, double value)
{
element.SetValue(BorderRoundnessProperty, value);
}
public static double GetBorderRoundness(UIElement element)
{
return (double)element.GetValue(BorderRoundnessProperty);
}
}
You can refer to the BorderRoundness using attached property binding syntax (parentheses).
<Style x:Key="AwakeButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="local:ButtonProperties.BorderRoundness" Value="4.0"/>
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="{Binding (local:ButtonProperties.BorderRoundness), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
</Style>
</Style.Resources>
</Style>
You use regular button now with the newly created attached border roundness property.
<Button Grid.Row="0" Style="{StaticResource AwakeButton}" Margin="142,115,0,0" Width="136" Height="167" local:ButtonProperties.BorderRoundness="5"/>
The roundness is applied as CornerRadius to the Border of the Button. The Border is defined in the ControlTemplate of the Button. The ControlTemplate defines the appearance of a control.
In other words, you need to delegate the property values to the related elements in the ControlTemplate.
To delegate the values to the ControlTemplate, you have to override this template and bind the templated parents properties to the template elements:
In your AwakeButton define the BorderRoundness property as simple DependencyProperty (not attached) and the override the default style definition, so that the AwakeButton will use its own default Style. This way the Button is reusable withgout having to redefine the Style each time you weant to use it, which is especially important wjen you publish your project as library:
AwakeButton.cs
public class AwakeButton : Button
{
public static readonly DependencyProperty BorderRoundnessProperty = DependencyProperty.Register(
"BorderRoundness",
typeof(Thickness),
typeof(AwakeButton),
new PropertyMetadata(default(Thickness)));
public Thickness DestinationPath
{
get => (Thickness) GetValue(AwakeButton.BorderRoundnessProperty);
set => SetValue(AwakeButton.BorderRoundnessProperty, value);
}
static AwakeButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AwakeButton), new FrameworkPropertyMetadata(typeof(AwakeButton)));
}
}
Generic.xaml.cs
This file is located in the Themes folder and contains all default styles. WPF will automatically check this file for a default style and apply it if no other Style override was found.
<Style TargetType="AwakeButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="AwakeButton">
<Border BorderBrush={TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding BorderRoundness}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Stayle>
Example
<Grid>
<AwakeButton BorderRoundness="8" />
</Grid>
But if you want to make it really general, using an attached property, you have to make a attached behavior. The following code works with every DependencyObject that contains a Border as child in its visual tree:
class Element : DependencyObject
{
#region CornerRoundness attached property
public static readonly DependencyProperty CornerRoundnessProperty = DependencyProperty.RegisterAttached(
"CornerRoundness",
typeof(CornerRadius),
typeof(Element),
new PropertyMetadata(default(CornerRadius), Element.OnCornerRoundnessChanged));
public static void SetCornerRoundness(DependencyObject attachingElement, CornerRadius value) =>
attachingElement.SetValue(Element.CornerRoundnessProperty, value);
public static CornerRadius GetCornerRoundness(DependencyObject attachingElement) =>
(CornerRadius) attachingElement.GetValue(Element.CornerRoundnessProperty);
#endregion CornerRoundness attached property
private static void OnCornerRoundnessChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (Element.TryFindVisualChildElement(attachingElement, out Border elementBorder))
{
elementBorder.CornerRadius = (CornerRadius) e.NewValue;
}
}
public static bool TryFindVisualChildElement<TChild>(DependencyObject parent, out TChild resultElement)
where TChild : DependencyObject
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild child)
{
resultElement = child;
return true;
}
if (Element.TryFindVisualChildElement(childElement, out resultElement))
{
return true;
}
}
return false;
}
}
Example
<StackPanel>
<Button Element.CornerRoundness="8" />
<ToggleButton Element.CornerRoundness="8" />
</StackPanel>

How to link InkToolbar to an InkCanvas which is inside CustomControl?

I am creating a CustomControl which contain InkCanvas. Now the problem is How do I link InkToolbar(which is outside the CustomControl) to an InkCanvas(which is inside the CustomControl)?
Solution Tried:
I tried to get the InkCanvas outside the CustomControl using below code but It is not working.
Here is my code(With the solution I tried which is not working):
//In CustomControl Code Behind
InkCanvas PATH_INK_CANVAS;
protected override void OnApplyTemplate()
{
PATH_INK_CANVAS = GetTemplateChild<InkCanvas>("PATH_INK_CANVAS");
}
T GetTemplateChild<T>(string elementName) where T : DependencyObject
{
var element = GetTemplateChild(elementName) as T;
if (element == null)
throw new NullReferenceException(elementName);
return element;
}
public InkCanvas InkCanvas
{
get { return PATH_INK_CANVAS; }
}
public static readonly DependencyProperty InkCanvasProperty =
DependencyProperty.Register("InkCanvas", typeof(InkCanvas), typeof(RichInkTextBox), new PropertyMetadata(0));
//In CustomControl XAML
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Name="MainGrid" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<InkCanvas Name="PATH_INK_CANVAS" Canvas.ZIndex="-1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
//In Page
<local:CustomControl x:Name="MyCustomControl"/>
<InkToolbar Grid.Row="0" TargetInkCanvas="{x:Bind MyCustomControl.InkCanvas}"/>
I don't think that's the right syntax to define a read-only dependency property. Try something like the following instead -
public InkCanvas InkCanvas
{
get => (InkCanvas)GetValue(InkCanvasProperty);
private set => SetValue(InkCanvasProperty, value);
}
public static readonly DependencyProperty InkCanvasProperty = DependencyProperty.Register(
"InkCanvas", typeof(InkCanvas), typeof(InkCanvasWrapper), new PropertyMetadata(null));
Also, make sure you set the Mode of the x:Bind to OneWay as the default value of the InkCanvas dependency property is null (you are setting the default value to 0 which is wrong).
<InkToolbar Grid.Row="0" TargetInkCanvas="{x:Bind MyCustomControl.InkCanvas, Mode=OneWay}" />

Update Normal Property in Dependency property/AttachedProperty,

I am trying to bind a normal property of AvalonDock,
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
<xcad:LayoutAnchorable Title="Folder" CanHide="{Binding IsHideExplorerView}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</xcad:LayoutAnchorable>
Here CanHide is a Normal property, if trying to bind will throw the exception like
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
My question is, Is it possible any way to make a normal property to override DependencyProperty to make it Bindable.
Edit
Added a class which inherit LayoutAnchorable but PropertyChangedCallback of DependencyProperty Never calls.
public class ExtendedAnchorableItem : LayoutAnchorable
{
public static readonly DependencyProperty IsCanHideProperty =
DependencyProperty.RegisterAttached("IsCanHide", typeof(bool), typeof(ExtendedAnchorableItem),
new FrameworkPropertyMetadata((bool)false,
new PropertyChangedCallback(OnCanHideChanged)));
public bool IsCanHide
{
get { return (bool)GetValue(IsCanHideProperty); }
set { SetValue(IsCanHideProperty, value);
this.IsVisible = value; // No effect.
}
}
private static void OnCanHideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ExtendedAnchorableItem)d).Hide();
}
}
XAML
<xcad:LayoutAnchorablePane>
<Utility:ExtendedAnchorableItem IsCanHide="{Binding IsHideExplorer}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</Utility:ExtendedAnchorableItem>
</xcad:LayoutAnchorablePane>
Similarly i have tried creating an AttachedProperty which can hook it to LayoutAnchorable but PropertyChangedCallback Never get called click here for a new question i have posted.
Any Help guys ?
I did and example previously in my case i need to create new button with 2 images one when the button is available and the other one when it's disabled, to do that first i created new user control named "MyButton" my xaml was like this
<Button ToolTip="{Binding ButtonLabel,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Command="{Binding ButtonCommand,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Cursor="Hand" VerticalAlignment="Center" >
<Button.Template>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="45"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Name="ButtonImage" IsEnabled="{Binding Path=IsEnabled,RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Source" Value="{Binding ActiveImage,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" Value="{Binding DeactiveImage,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Label Name="LabelContent" Content="{Binding ButtonLabel,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1" IsEnabled="{Binding Path=IsEnabled,RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" VerticalContentAlignment="Center" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
then i added dependency Properties for ActiveImage and DeactiveImage using this code
public static DependencyProperty activeImage =
DependencyProperty.Register("ActiveImage", typeof(type of this property like "string"), typeof(type of the custom control that you need like "MyButton"), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string ActiveImage
{
get { return (string)GetValue(activeImage); }
set { SetValue(activeImage, value); }
}
then i used this new control in my project
<custom:MyButton ButtonCommand="{Binding DecreaseImagesCount}" ButtonLabel="ZoomIn" ActiveImage="/Images/ActiveImages/ZoomIn.png" DeactiveImage="/Images/GrayImages/ZoomIn.png"
Grid.Column="2" Margin="3,4" />
notice that i can do binding the path for Button Image now
If it is enough for you to just set that property from your view model then you could use an attached behavior.
Just create a new class and add an attached property like this (I did not really test this, since I actually do not have AvalonDock at hand, but you should get the idea):
public class YourBehavior
{
public static readonly DependencyProperty YourCanHideProperty = DependencyProperty.RegisterAttached(
"YourCanHide",
typeof(bool),
typeof(LayoutAnchorable),
new PropertyMetadata(YourCanHidePropertyChanged));
private static void YourCanHidePropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
LayoutAnchorable control = dependencyObject as LayoutAnchorable;
if (control != null)
{
control.CanHide = e.NewValue as bool;
}
}
public static bool GetYourCanHideProperty(LayoutAnchorablewindow)
{
return window.GetValue(YourProperty) as bool?;
}
public static void SetYourCanHideProperty(LayoutAnchorable control, bool value)
{
window.SetValue(YourProperty, value);
}
}
Now you should be able to use that behavior like this:
<xcad:LayoutAnchorable Title="Folder" namespacealias:YourBehavior.YourCanHideProperty="{Binding IsHideExplorerView}"/>
If you want to have it working in both directions just check out the attached Blend behaviors.
Yes, you can do it.. you need to implement INotifypropertyChanged interface and raise a ProprtyChanged Event inside the property setter. After changing the property to a DependencyProperty, you will get the notification mechanism, so the property change is propagated to the target (in this case xcad) .
you can find lot of examples implementing the INotifyPropertyChanged..

Windows phone custom user control with template support

I am making a custom user control and I want the control to be reusable and a part of the control to be usable for adding new controls. What I am trying to make is a template in the custom user control in which the user can add new content.
I am using windows phone 8
How can I do this?
Add new Templated Control from "Add new item" menu.
You should get Generic.xaml file in Themes folder.
In Generic.xaml you have style for your custom control:
<Style TargetType="local:CustomControl1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomControl1">
Write your control xaml here
<Border x:Name="BorderNameTest"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Button IsEnabled="{TemplateBinding IsFancyLookEnabled}"></Button>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You'll also have .cs file for your custom control. To use controls (in code) from your custom control template xaml you need to use [TemplatePart] attribute on your "control class".
Snippet:
[TemplatePart(Name = BorderTestTemplatePartName, Type = typeof(Border))]
public sealed class CustomControl1 : Control
{
private const string BorderTestTemplatePartName = "BorderNameTest";
private Border _myBorder;
public static readonly DependencyProperty IsFancyLookEnabledProperty = DependencyProperty.Register(
"IsFancyLookEnabled", typeof (bool), typeof (CustomControl1), new PropertyMetadata(default(bool)));
public bool IsFancyLookEnabled
{
get { return (bool) GetValue(IsFancyLookEnabledProperty); }
set { SetValue(IsFancyLookEnabledProperty, value); }
}
public CustomControl1()
{
this.DefaultStyleKey = typeof(CustomControl1);
}
protected override void OnApplyTemplate()
{
_myBorder = GetTemplateChild(BorderTestTemplatePartName) as Border;
// attach events etc. (you can detach them using for example Unloaded event)
base.OnApplyTemplate();
}
}
Additionaly I have shown you how to expose properties on your control (so your control user can write <namespace:SuperControl IsFancyLookEnabled="True">). You create dependency property (as shown in code snippet) which you can use in your xaml with TemplateBinding - or just use in code.

A custom Textbox - WPF and Xaml

I want to make a custom TextBox using XAML and a custom class with an additional property to TextBox called PosType. PosType will be rendered inside the red triangle in the side.
The TextBox should be an ordinary textbox with enough margin from left to not intercept the other text.
Here is the Image showing the desired look of the textbox.
The Control class :
public class PosTextBox : TextBox
{
public string PosType { get; set; }
}
**The style I wrote : (quite similar approach to what I want except here I used border and other parts may not be accurate. **
xmlns:Pro="clr-namespace:Prox.XamlControls">
<!-- Custom TextBox -->
<Style x:Key="c1PosTextBox" TargetType="{x:Type Pro:PosTextBox}" >
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Pro:PosTextBox}">
<Grid>
<Border>
<Border>
<TextBlock Text ="{TemplateBinding Path= Pro:PosType}"></TextBlock>
<!--<TextBlock Text ="{TemplateBinding ElementName=Pro:PosTextBox, Path= Pro:PosType}"></TextBlock>-->
</Border>
</Border>
<Border Margin="5,10,5,10">
<ContentPresenter Name="Content" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" TextBlock.Foreground="White"></ContentPresenter>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How to create this custom textbox and achieve the desired look?
Edit:
Please guide me to fix the minor issues based on the same approach I mentioned above.
You could do that using Adorners
Adorners are rendered in a different layer called AdornerLayer on top of the UIElement, Which can get you the desired affect.
public class PosTypeAdorner : Adorner
{
private string _posText;
// Be sure to call the base class constructor.
public PosTypeAdorner (UIElement adornedElement, string posText) : base(adornedElement)
{
_posText = posText;
}
// A common way to implement an adorner's rendering behavior is to override the OnRender
// method, which is called by the layout system as part of a rendering pass.
protected override void OnRender(DrawingContext drawingContext)
{
// Draw the red triangle with it's text using the drawingContext here
}
}
Assuming you want the text of the PosType to be bindable, you should make it as a Dependency property.
Use OnApplyTemplate to attach the adorner to your text box
public class PosTextBox : TextBox
{
public PosTextBox()
{
}
public static readonly DependencyProperty PosTypeProperty =
DependencyProperty.Register("PosType", typeof (string), typeof (PosTextBox), new PropertyMetadata(default(string)));
public string PosType
{
get { return (string)GetValue(PosTypeProperty); }
set { SetValue(PosTypeProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var layer = AdornerLayer.GetAdornerLayer(this);
var posAdorner = new PosTypeAdorner(this, PosType);
layer.Add(posAdorner);
}
}
For more information, you can check out this links:
http://www.codeproject.com/Articles/54472/Defining-WPF-Adorners-in-XAML
http://www.nbdtech.com/Blog/archive/2010/06/21/wpf-adorners-part-1-ndash-what-are-adorners.aspx
http://www.nbdtech.com/Blog/archive/2010/06/28/wpf-adorners-part-2-ndash-placing-any-control-on-the.aspx
Good luck

Categories