I've created a type of Border called SelectableBorder that has an additional property called "IsSelected". I'm using this property in some triggers e.g.:
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Background" Value="{DynamicResource AccentColorBrush3}" />
</MultiTrigger.Setters>
However in the code in the background there is no way for me to set IsSelected, How do I go about creating a property that can be used in xaml triggers and in code in the background?
This is the current SelectableBorder code
public class SelectableBorder : Border
{
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached("IsSelected", typeof(bool), typeof(Border), new PropertyMetadata(false));
public static void SetIsSelected(UIElement element, bool value)
{
element.SetValue(IsSelectedProperty, value);
}
public static bool GetIsSelected(UIElement element)
{
return (bool)element.GetValue(IsSelectedProperty);
}
}
I am also seeing:
'SelectableBorder' initialization failed: The type initializer for 'SelectableBorder' threw an exception.
Which suggests to me that I'm not doing that well at my first attempt. Could you please guide me in the correct direction to solving these problems?
Since you're adding property to DependencyObject so you can use normal DependencyProperty instead of attached one. You can use attached if you want but then trigger should change. Also owner type of your property should be SelectableBorder instead of Border. You can also add IsSelected CLR wrapper do make it easier to set/get value in code behind.
public class SelectableBorder : Border
{
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register("IsSelected", typeof(bool), typeof(SelectableBorder), new PropertyMetadata(false));
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
}
Related
This question already has answers here:
Template Binding with Attached Properties
(2 answers)
Closed 1 year ago.
To easily change the template-specific brushes of a button without directly changing the template, I decided to make a DependencyProperty that will bind to a template-specific brush. That way, I can change this brush just as easy as changing any other regular property. However, after implementing this DependencyProperty, I encountered an error: "Name "ExtensionClass" does not exist in namespace "clr-namespace:extensions"." What causes this error?
XAML:
<ResourceDictionary xmlns:ext="clr-namespace:Extensions"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ControlTemplate x:Key="ButtonBaseControlTemplate1" TargetType="{x:Type ButtonBase}">
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="border" Value="{TemplateBinding Property=ext:ExtensionsClass.MouseOverBackground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>
C#:
namespace Extensions {
public class ExtensionsClass {
public static readonly DependencyProperty MouseOverBackgroundProperty = DependencyProperty.Register("MouseOverBackground", typeof(Brush), typeof(Button));
public static void SetMouseOverBackground(UIElement element, Brush value) {
element.SetValue(MouseOverBackgroundProperty, value);
}
public static Brush GetMouseOverBackground(UIElement element) {
return (Brush)element.GetValue(MouseOverBackgroundProperty);
}
}
}
In addition to the problem with the Binding, which is covered in the answer to the duplicate question, you also have to be aware that you are declaring an attached property, which has to be registered with the RegisterAttached method.
Besides that, in both the Register and the RegisterAttached methods, the third argument has to be the type that declares the property, not the type of element where you intend to set the property, i.e. typeof(ExtensionsClass) here.
public static class ExtensionsClass
{
public static readonly DependencyProperty MouseOverBackgroundProperty =
DependencyProperty.RegisterAttached(
"MouseOverBackground",
typeof(Brush),
typeof(ExtensionsClass),
null);
public static void SetMouseOverBackground(UIElement element, Brush value)
{
element.SetValue(MouseOverBackgroundProperty, value);
}
public static Brush GetMouseOverBackground(UIElement element)
{
return (Brush)element.GetValue(MouseOverBackgroundProperty);
}
}
You bind to an attached property by means of a Binding Path with parentheses:
<Setter
Property="Background"
TargetName="border"
Value="{Binding Path=(ext:ExtensionsClass.MouseOverBackground),
RelativeSource={RelativeSource TemplatedParent}}"/>
I have a custom silverlight control that is pretty much a glorified text box. I have some properties in it I need to be able to set from the XAML in silverlight, so for each property I have created something like this:
public bool UseCustomTooltips
{
get { return _useCustomTooltips; }
set
{
_useCustomTooltips = value;
DoSomeOtherStuff();
}
}
public static readonly DependencyProperty UseCustomTooltipsProperty = DependencyProperty.Register("UseCustomTooltips",
typeof(bool), typeof(MyControl), new PropertyMetadata(false, PropertyChangedCallback));
In the XAML I can create the control and specify a value for that property like this:
<Controls:MyControl UseCustomTooltips="True" />
The control's state is update, my callback is hit, and all is as it should be. However, when I try to specify that state from a style instead of in each instance of the control like this:
<Style x:Key="MyControl" TargetType="Controls:MyControl">
<Setter Property="Foreground" Value="Yellow"/>
<Setter Property="UseCustomTooltips" Value="True"/>
</Style>
<Controls:MyControl Style="{StaticResource MyControl}" />
my custom properties are never set and my callback is never hit even though the inherited property Foreground takes on the value specified in the style. This leads me to assume there is something wrong with the way my dependency property is wired up but everything I've seen so far online tells me I have it correct.
Can anyone point me in the right direction?
Thanks.
That is not the correct way to register a dependency property.
public bool UseCustomTooltips
{
get { return (bool)GetValue(UseCustomTooltipsProperty); }
set { SetValue(UseCustomTooltipsProperty, value); }
}
public static readonly DependencyProperty UseCustomTooltipsProperty =
DependencyProperty.Register("UseCustomTooltips",
typeof(bool),
typeof(MyControl),
new PropertyMetadata(false, new PropertyChangedCallback(MyCallbackMethod)));
Use the propdp snippet, it really is a beautiful thing.
I'd like to extend Image class by adding second source. I want to define second source in XAML (like original source) and change these images when mouse enters/leaves this image.
I tried myself with:
class MainMenuImageButton : Image
{
public static readonly DependencyProperty Source2Property;
public ImageSource Source2
{
get { return Source2; }
set
{
this.MouseEnter+=new System.Windows.Input.MouseEventHandler(MainMenuImageButton_MouseEnter);
}
}
public void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = Source2;
}
}
But it doesn't work and I think I do it tottaly wrong. Can somebody help?
[UPDATE]
I wrote this:
class MainMenuImageButton : Image
{
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
var source = (BitmapSource)Source;
var x = (int)(hitTestParameters.HitPoint.X / ActualWidth * source.PixelWidth);
var y = (int)(hitTestParameters.HitPoint.Y / ActualHeight * source.PixelHeight);
var pixels = new byte[4];
source.CopyPixels(new Int32Rect(x, y, 1, 1), pixels, 4, 0);
if (pixels[3] < 10) return null;
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
public ImageSource Source1
{
get { return GetValue(ImageSourceProperty) as ImageSource; }
set { base.SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("Source1", typeof(ImageSource), typeof(MainMenuImageButton));
public ImageSource Source2
{
get { return GetValue(ImageSource2Property) as ImageSource; }
set { base.SetValue(ImageSource2Property, value); }
}
public static readonly DependencyProperty ImageSource2Property = DependencyProperty.Register("Source2", typeof(ImageSource), typeof(MainMenuImageButton));
public MainMenuImageButton() : base()
{
this.MouseEnter += new MouseEventHandler(MainMenuImageButton_MouseEnter);
this.MouseLeave += new MouseEventHandler(MainMenuImageButton_MouseLeave);
}
void MainMenuImageButton_MouseLeave(object sender, MouseEventArgs e)
{
this.Source = this.Source1;
}
void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = this.Source2;
}
}
But sometimes it works and sometimes there is exception: "An unhandled exception of type 'System.ArgumentException' occurred in PresentationCore.dll
Additional information: The value is outside the expected range."
I'm not sure if I understood, but I tried this:
class MainMenuImageButton : Image
{
public static readonly DependencyProperty Source2Property = DependencyProperty.Register("Source2", typeof(ImageSource), typeof(MainMenuImageButton), new PropertyMetadata(true));
public ImageSource Source2
{
get { return (ImageSource)GetValue(Source2Property); }
set
{
BitmapImage logo = new BitmapImage(new Uri(value.ToString(), UriKind.Relative));
SetValue(Source2Property, logo);
this.MouseEnter+=new System.Windows.Input.MouseEventHandler(MainMenuImageButton_MouseEnter);
}
}
public void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = Source2;
}
}
And still nothing. Wham am I doing wrong?
Refer to the Custom Dependency Properties article on MSDN. The event hookup belongs in your dependency property's PropertyChangedCallback.
I would also suggest using a trigger instead of event handling. However, this doesn't mean you will need to duplicate the XAML everywhere you want to use it. You could define a custom control with the image switching trigger in its default style (see "Defining Resources at the Theme Level" in the Control Authoring Overview). Where MouseOverImage is a Control with "Source" and "Source2" dependency properties, you could define this default style:
<Style TargetType="local:MouseOverImage">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MouseOverImage">
<Grid>
<Image Name="SourceImage" Source="{TemplateBinding Source}" />
<Image Name="Source2Image" Source="{TemplateBinding Source2}" Visibility="Hidden" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="SourceImage" Property="Visibility" Value="Hidden" />
<Setter TargetName="Source2Image" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If you use event handlers, you would need to store the original value of Source, add a MouseLeave handler that reverts it, and also consider the case where a user reassigns Source or Source2 at any time. Using the trigger solution with two separate "Source" and "Source2" bindings, all of this is handled automatically.
EDIT
But sometimes it works and sometimes there is exception: "An unhandled
exception of type 'System.ArgumentException' occurred in
PresentationCore.dll
Additional information: The value is outside the expected range."
My guess is that HitTestCore is firing after the source changes but before it's applied to the layout, so there is a discrepancy between ActualWidth and source.PixelWidth. I am not sure of the rationale for including these in the calculation (shouldn't they always be the same?) Try just using the following:
var x = (int)hitTestParameters.HitPoint.X;
var y = (int)hitTestParameters.HitPoint.Y;
Extending Image Is an overkill, all you have to do is define a style which will use trigger to swap the sources
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Image1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="Image2"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
You don't need to extend the Image class to do this. There is a property on the Image class called IsMouseOver that you can trigger on to switch the Source of your image. Put this in a style on your view and you'll be all set.
You need to add the new property as a Dependency Property. You can find out more from the DependencyProperties Overview page at MSDN, but the basic idea is this:
You first create the Dependency Property:
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register(
"IsSpinning", typeof(Boolean), typeof(YourClassName), new PropertyMetadata(true));
Then you can optionally add a wrapper using standard CLR properties (for your own use only):
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
(Code example was taken from the linked article)
I got another problem while working with my usercontrol's xaml file -.-'
I tried to implement an IsChecked property to my custom button in order to set a different background colour if the button is checked.
So I created a DependencyProperty like this:
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(LeftMenuBtn));
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
Then I setup a new style trigger to handle this property:
<Style x:Key="ButtonEnableStates" TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="{DynamicResource CheckedStateGradient}" />
</Trigger>
</Style.Triggers>
</Style>
Expression Blend now underlines Property="IsChecked" and says:
The member "IsChecked" is not recognized or is not accessible.
How can I solve this problem?
Well, the Style's TargetType is Grid and the property is defined for LeftMenuBtn, not going to work like that.
From a custom control based on TextBox, I created a property named Items, in this way:
public class NewTextBox : TextBox
{
public ItemCollection Items { get; set; }
}
When using the custom control in XAML, I cannot bind the property because it raises exception "A 'Binding' can only be set on a DependencyProperty of a DependencyObject.".
How do I solve this exception?
As a side note, it is also worth noting that you will get these binding errors if you copy and paste between objects and forget to change the second typeof(Object) statement.
I couldn't figure out for a good hour why I was getting this error as everything appeared to be defined and correct. I'd moved my properties into a usercontrol as I wanted to go from a single set to a list. Thus:
public static readonly DependencyProperty FoldersProperty = DependencyProperty
.Register("Folders", typeof(OutlookFolders), typeof(MainWindow),
new FrameworkPropertyMetadata(new OutlookFolders()));
public OutlookFolders Folders
{
get { return GetValue(FoldersProperty) as OutlookFolders; }
set { SetValue(FoldersProperty, value); }
}
Should have become:
public static readonly DependencyProperty FoldersProperty = DependencyProperty
.Register("Folders", typeof(OutlookFolders), typeof(SavedFolderControl),
new FrameworkPropertyMetadata(new OutlookFolders()));
public OutlookFolders Folders
{
get { return GetValue(FoldersProperty) as OutlookFolders; }
set { SetValue(FoldersProperty, value); }
}
Until I did this change I kept receiving the error:
A 'Binding' cannot be set on the property 'Folders' of type 'SavedFolderControl'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
To solve this exception you need to change the property Items and add a DependencyProperty that will work as a "link" in XAML. The class will be:
public class AutocompleteTextBox : TextBox
{
public ItemCollection Items
{
get {
return (ItemCollection)GetValue(ItemsProperty); }
set {
SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(ItemCollection),
typeof(AutocompleteTextBox),
new PropertyMetadata(default(ItemCollection), OnItemsPropertyChanged));
private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// AutocompleteTextBox source = d as AutocompleteTextBox;
// Do something...
}
Here's another gotcha: Ensure that the string in the first argument of DependencyProperty.Register() matches the name of the related property.
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"TheItems", // This is wrong
typeof(ItemCollection),
typeof(AutocompleteTextBox),
new PropertyMetadata(default(ItemCollection), OnItemsPropertyChanged));
I ran into this issue when I renamed my property without changing the string.
One thing I noticed, and I am not sure it is mentioned anywhere, is that the name of your DependencyProperty must match your property name
If your property name is Items, then you DependencyProperty must be ItemsProperty
In my case, as soon as I matched them the error went away
Another potential cause of this is when you provide a bad type for the default value in the metadata.
For instance:
new PropertyMetadata(default(ItemCollection), OnItemsPropertyChanged)
would throw this error if you wrote instead:
new PropertyMetadata(false, OnItemsPropertyChanged)
This can also happen if you are copying and pasting from a code source.
I had the (runtime + designtime) message:
An unhandled exception of type
'System.Windows.Markup.XamlParseException' occurred in
PresentationFramework.dll
Additional information: A 'Binding' cannot be set on the 'Property'
property of type 'Trigger'. A 'Binding' can only be set on a
DependencyProperty of a DependencyObject.
Where I was smart enough to define a Trigger on a VM property..
// incorrect.. cannot have Trigger for VM property
<Trigger Property="{Binding IsExpanded}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</Trigger>
Which should of course be a datatrigger (which uses Binding instead of Property)
<DataTrigger Binding="{Binding IsExpanded}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
Triggers are typically for Controls (Button, TextBox, FrameworkElement etc.) properties.
I had this issue due to a lack of oversight on my part. I wrote
<Button.Visibility>
<MultiBinding Converter="{StaticResource mvbConverter}">
<Binding Path="{Binding isActive}" />
<Binding Path="{Binding isCashTransaction}" />
</MultiBinding>
</Button.Visibility>
when instead i should've wrote
<Button.Visibility>
<MultiBinding Converter="{StaticResource mvbConverter}">
<Binding Path="isActive" />
<Binding Path="isCashTransaction" />
</MultiBinding>
</Button.Visibility>