Setter but not for Style in WPF? - wpf

I know how Triggers with Setters work in WPF, and I know that Setters can only change Style properties. Is there something equivalent to the Setter for non-Style properties? I really want to be able to change a property on custom object that is instantiated in XAML. Any ideas?
Edit: While Setters can update any dependency property, I am attempting to do this within an EventTrigger, which I forgot to specify. There is this workaround, but I'm not sure if it is really best practice. It uses storyboards and ObjectAnimationUsingKeyFrames. Is there anything wrong with this?

Using Interactivity from the Blend SDK you can do this in XAML, you only need to create a TriggerAction which sets the property.
Edit: There already is such an action in another namespace: ChangePropertyAction
In XAML you can use this namespace: http://schemas.microsoft.com/expression/2010/interactions
Tested example:
public class PropertySetterAction : TriggerAction<Button>
{
public object Target { get; set; }
public string Property { get; set; }
public object Value { get; set; }
protected override void Invoke(object parameter)
{
Type type = Target.GetType();
var propertyInfo = type.GetProperty(Property);
propertyInfo.SetValue(Target, Value, null);
}
}
<StackPanel>
<StackPanel.Resources>
<obj:Employee x:Key="myEmp" Name="Steve" Occupation="Programmer"/>
</StackPanel.Resources>
<TextBlock>
<Run Text="{Binding Source={StaticResource myEmp}, Path=Name}"/>
<Run Name="RunChan" Text=" - "/>
<Run Text="{Binding Source={StaticResource myEmp}, Path=Occupation}"/>
</TextBlock>
<Button Content="Demote">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<t:PropertySetterAction Target="{StaticResource myEmp}"
Property="Occupation"
Value="Coffee Getter"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
Note that with Value being an object default ValueConversion will not take place, if you enter a value as an attribute (Value="Something") it will be interpreted as a string. To set an int for example you can do this:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<t:PropertySetterAction Target="{StaticResource myEmp}"
Property="Id">
<t:PropertySetterAction.Value>
<sys:Int32>42</sys:Int32>
</t:PropertySetterAction.Value>
</t:PropertySetterAction>

Have you declared the properties that you want to set as dependency properties? I can't find the project I did this in, but I am pretty sure that is what fixed it for me.
I tried implementing something pretty simple, and got the following:
The property "Type" is not a DependancyProperty. To be used in markup, non-attached properties must be exposed on the target type with and accessible instance property "Type". For attached properties the declaring type must provide static "GetType" and "SetType" methods.
Here is a dependancy property registration example from another project of mine:
Public Shared TitleProperty As DependencyProperty = DependencyProperty.Register("Title", GetType(String), GetType(SnazzyShippingNavigationButton))
In the above example, SnazzyShippingNavigationButton is the Class Name of which the property is a member.
And the associated property declaration:
<Description("Title to display"), _
Category("Custom")> _
Public Property Title() As String
Get
Return CType(GetValue(TitleProperty), String)
End Get
Set(ByVal value As String)
SetValue(TitleProperty, value)
End Set
End Property
Description and Category attributes only really apply to the IDE designer property grid display.

Related

Can we implement dependency property in a class that does not inherit from DependencyObject? If yes, what is the difference?

Can we implement dependency property in a class that does not inherit from DependencyObject? If yes what is the difference?
Yes you can. This is a variant of an Attached Property. From How to Create an Attached Property:
If your class is defining the attached property strictly for use on other types, then the class does not have to derive from DependencyObject. But you do need to derive from DependencyObject if you follow the overall WPF model of having your attached property also be a dependency property.
The difference begins in how you define an attached property. Instead of Register, you have to use the RegisterAttached method and you have to define the get and set accessors as static methods with the following naming convention, where PropertyName is the name of the attached property.
public static object GetPropertyName(object target)
public static void SetPropertyName(object target, object value)
Let's look at a simple example. Suppose you want create a button that shows an image and a text side-by side. Unfortunately, a Button only has a single Content. As you do not want to create a custom control right now, you try to solve the issue by creating a content template and an attached property for the path of an image to display.
public static class IconButtonProperties
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source", typeof(string), typeof(IconButtonProperties));
public static void SetSource(UIElement element, string value)
{
element.SetValue(SourceProperty, value);
}
public static string GetSource(UIElement element)
{
return (string) element.GetValue(SourceProperty);
}
}
Now you can attach this property to a button in your view to define an image path. Here the attached property differs in that you define it on a different type (Button) using its owner type IconButtonProperties.
<Button ContentTemplate="{StaticResource ImageTextContentTemplate}"
local:IconButtonProperties.Source="Resources/MyImage.png"
Content="Click me!"/>
The last big difference is shown in the data template that uses the attached property with a Binding. When binding to an attached property, you have to put the property in parentheses.
<DataTemplate x:Key="ImageTextContentTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding (local:IconButtonProperties.Source), RelativeSource={RelativeSource AncestorType={x:Type Button}}}"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Margin="5, 0, 0, 0"
Text="{Binding}"/>
</Grid>
</DataTemplate>
As you can see, attached properties are invaluable for extensibility and binding in WPF. For more information on attached properties in general, you can refer to the documentation:
Attached Properties Overview
How to: Register an Attached Property
Binding Path Syntax
DependencyProperty.RegisterAttached Method
DependencyProperty.RegisterAttachedReadOnly Method

Difference Source and Datacontext in wpf

DataContext and Source seem to be very similar to me.
What are the advantages and disadvantages?
When to use which one?
With Source:
<TextBlock Text="{Binding Name, Source={StaticResource Person}}" />
Or the solution with DataContext:
public partial class DataContextSample : Window
{
public string Name {get; set;}
public DataContextSample()
{
InitializeComponent();
this.DataContext = this;
}
}
<TextBlock Text="{Binding Name}" />
A binding with out a specified Source binds to the DataContext property of the element.
The DataContext is a special property which, if not set, is redirected to the element's parent's DataContext. This prevents duplicate xaml (always setting the Source in every binding) and makes all bindings relative so it is easier to change the UI without having to adjust all Sources in the bindings.

Using a variable in XAML binding expression

I'm building a control that can edit POCOs. There is a descriptor collection for the fields within the POCO that need to be edited and I'm binding a ListBox's ItemsSource to this collection. Amongst other things, the descriptor gives me the ability to select a suitable DataTemplate and the variable name in the POCO that this ListBox item should edit.
My ListBox is built like this:
<ListBox ItemsSource="{Binding ColumnCollection, ElementName=root}">
<ListBox.Resources>
<DataTemplate x:Key="TextTemplate">
<StackPanel>
<TextBlock Text="{Binding DisplayName}" />
<!-- !!! Question about following line !!! -->
<TextBox Text="{Binding ElementName=vm.CurentEditing, Path=PathName}" />
</StackPanel>
</DataTemplate>
<!-- Details omitted for brevity -->
<DataTemplate x:Key="PickListTemplate" />
<DataTemplate x:Key="BooleanTemplate" />
</ListBox.Resources>
<ListBox.ItemTemplateSelector>
<local:DataTypeSelector
TextTemplate="{StaticResource TextTemplate}"
PickListTemplate="{StaticResource PickListTemplate}"
BooleanTemplate="{StaticResource BooleanTemplate}"
/>
</ListBox.ItemTemplateSelector>
</ListBox>
It is the TextBox binding expression in the "TextTemplate" that I am having problems with. The problem is that "PathName" should not be taken as a literal string, but is the name of a string property in the ColumnDescription class (the collection type of ColumnCollection used for ListBox.ItemsSource), which gives the name of the POCO property I want to bind to (the POCO is "vm.CurrentEditing").
Is there some way to use the value of a property in XAML as input to a binding expression, or will I have to resort to code behind?
(Incidentally, specifying the ElementName as "x.y" as I have done above also seems to be invalid. I assume the "y" part should be in Path but that's currently taken up with my property name...!)
So you want to bind TextBox.Text to Property X of Object Y, where X and Y both change at runtime.
It sounds like what you want to do is something analogous to ListBox.DisplayMemberPath: You can bind a string or PropertyPath property to DisplayMemberPath and it'll work. The way I've done stuff like that is to have a dependency property of type String or PropertyPath, and programatically create a binding from that to whatever property.
So, I wrote an attached property which creates a binding.
public class POCOWrangler
{
#region POCOWrangler.BindPropertyToText Attached Property
public static String GetBindPropertyToText(TextBox obj)
{
return (String)obj.GetValue(BindPropertyToTextProperty);
}
public static void SetBindPropertyToText(TextBox obj, PropertyPath value)
{
obj.SetValue(BindPropertyToTextProperty, value);
}
public static readonly DependencyProperty BindPropertyToTextProperty =
DependencyProperty.RegisterAttached("BindPropertyToText", typeof(String), typeof(POCOWrangler),
new PropertyMetadata(null, BindPropertyToText_PropertyChanged));
private static void BindPropertyToText_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is String && d is TextBox)
{
var tb = d as TextBox;
var binding = new Binding((String)e.NewValue);
// The POCO object we're editing must be the DataContext of the TextBox,
// which is what you've got already -- but don't set Source explicitly
// here. Leave it alone and Binding.Source will be updated as
// TextBox.DataContext changes. If you set it explicitly here, it's
// carved in stone. That's especially a problem if this attached
// property gets initialized before DataContext.
//binding.Source = tb.DataContext;
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(tb, TextBox.TextProperty, binding);
}
}
#endregion POCOWrangler.BindPropertyToText Attached Property
}
And I wrote a quick example thing: There's a little class named Foo that has a Name property, and a viewmodel with two properties, Foo Foo and String DisplayPathName. It works! Of course, this depends on default TextBox editing behavior for whatever type the property happens to be. I think that will get you the same results as if you'd bound explicitly in XAML, but it sitll won't always necessarily be just what you want. But you could very easily go a little nuts and add some triggers in the DataTemplate to swap in different editors, or write a DataTemplateSelector.
I stuffed ViewModel.Foo in a ContentControl just to get a DataTemplate into the act, so that the TextBox gets his DataContext in the same manner as yours.
Note also that I'm getting DisplayPathName by a relative source from something outside the DataContext object -- it's not a member of Foo, of course, it's a member of the viewmodel.
C#
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel {
DisplayPathName = "Name",
Foo = new Foo { Name = "Aloysius" }
};
}
XAML
<ContentControl
Content="{Binding Foo}"
>
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBox
local:POCOWrangler.BindPropertyToText="{Binding
DataContext.DisplayPathName,
RelativeSource={RelativeSource AncestorType=ContentControl}}"
/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
That was fun.

Silverlight Control Binding

Is there a way to bind a Silverlight control to an object (or database table's row) which contains the values of several control's properties, doing so without by define the binding for each property?
For instance:
Let's say I have the class (or entity based on database table's row) with the following values:
class TextBlockValues
{
public string Text{get; set;}
public string HorizontalAlignment{get; set;}
public string VerticalAlignment{get; set;}
}
I want to bind it to a TextBlock in my silverlight application (again without explicit specify the binding for each property).
Thank you for your time.
There are two parts in a binding: DataContext and the actual Binding objects. Once you set up the data context for an item, all the properties, and children will automatically use that.
For example:
<TextBlock Name="CaptionText" Text="{Binding Text}" HorizontalAlignment="{Binding HorizontalAlignment}" Height="20" TextAlignment="Center" FontStretch="Expanded" FontSize="13" />
And in the .cs file:
CaptionText.DataContext = myObject;
If I understand your question right the answer is no. Even though you can set the control's DataContext you still have to bind which property in the control binds to what in the class.

How to bind local variable in WPF

I have silverlight usercontrol. This contains Service Entity object. see below
public partial class MainPage : UserControl
{
public ServiceRef.tPage CurrentPage { get; set; }
...
}
I need to bind CurrentPage.Title to TextBox
My xaml is here
<TextBox Text="{Binding Path=CurrentPage.Title, RelativeSource={RelativeSource self}}"></TextBox>
But it is not work.
How to do it?
In order for that to work, you'll have to implement INotifyPropertyChanged on your class and raise the PropertyChanged event for CurrentPage when it's set (this also means you won't be able to use auto properties; you'll have to use your own private instance backing variable and code the get { } and set { } yourself).
What's happening is the control is binding to the value before you've set CurrentPage. Because you aren't notifying anyone that the property has changed, it does not know to refresh the bound data. Implementing INotifyPropertyChanged will fix this.
Or you could just manually set the Text property yourself in the setter.
Change your markup to
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=CurrentPage.Title}" />
By assigning RelativeSource={RelativeSource self} your are telling the TextBlock to bind to itself and look for a property named CurrentPage on the TextBlock itself and not the parent Window.
set the UpdateSourceTrigger="PropertyChanged" in the XAML.

Resources