Trigger when class field changed - wpf

I have created a DataGridCellTemplate where I have an Image control. By default it's Source property is X. I fill DataGrid with objects of my own class (have also implemented INotifyPropertyChanged interface).
I would like to change Source property of Image control when some boolean variable changes from False to True.
Should I use a trigger? If yes, how? Or maybe it should be done in c# code?
I could make 2 image controls, bind and control their Visible property, but it's lame solution I think.
Would appreciate any help.

In your DataTemplate try the following:
<DataTemplate>
<Image Name="Image" Source="X"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding BooleanProperty}" Value="True">
<Setter Property="Source" TargetName="Image" Value="Y" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Where BooleanProperty is the property that triggers the source-shift. Note that the Image must have a name - and that should be used in the Setter-tag. In the example - I change the source from 'X' to 'Y'
Hope this helps!

You should see if a Converter will do what you're wanting. You write one in code by creating a class which implements the IValueConverter interface (MSDN has an example on their site).
You would then declare the ValueConverter as a StaticResource like the following (you'll have to declare the local namespace if you don't already have it):
<local:BoolToImageConverter x:Key="imageConverter" />
To use it, you then bind the ImageControl's Source property to the Boolean property and specify the converter in the binding. An example follows:
<Image Source={Binding Path=IsImageShown, Converter={StaticResource imageConverter}} />
One more thing to be aware of is that the converter cannot just return a string containing a URI to an image location. It should return an ImageSource such as a BitmapImage.

Related

Why do I need to specify ElementName and DataContext in a binding?

To familiarize myself with WPF and MVVM concepts I built a visual representation of a Sudoku board.
My (simplified) setup looks like this (no custom code-behind in views anywhere):
I have a MainWindow.xaml:
<Window x:Class="Sudoku.WPF.MainWindow">
<Window.DataContext>
<models:MainWindowViewModel/>
</Window.DataContext>
<ctrl:SudokuBoard DataContext="{Binding Path=GameViewModel}"/>
</Window>
My MainWindowViewModel:
class MainWindowViewModel
{
public MainWindowViewModel()
{
IGame g = new Game(4);
this.GameViewModel = new GameViewModel(g);
}
public IGameViewModel GameViewModel { get; private set; }
}
SudokuBoard is a UserControl. Its DataContext is set to GameViewModel as per above.
Relevant parts of GameViewModel, Elements is populated in the ctor, Possibilities is set via a command:
public IList<CellViewModel> Elements { get; private set; }
private bool _showPossibilities;
public bool ShowPossibilities
{
get { return _showPossibilities; }
set
{
_showPossibilities = value;
OnPropertyChanged();
}
}
In SudokuBoard.xaml I have:
<ItemsControl x:Name="SudokuGrid" ItemsSource="{Binding Path=Elements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource ToggleContentStyle}"
Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Elements is a collection of CellViewModels generated in the constructor of GameViewModel.
Now to the question: my ToggleContentStyle as defined in <UserControl.Resources>:
<Style x:Key="ToggleContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource valueTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource possibilityTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
(both ContentTemplates just show other properties of a single CellViewModel in different representations)
Question 1: I have to explicitly reference DataContext in order to get to the ShowPossibilities property. If I leave it out, so that Path=ShowPossibilities, I get a UniformGrid with the ToString() representation of CellViewModel. My assumption is that that is because the style is referenced from the ItemTemplate, with it's binding set to a single CellViewModel. Is that assumption valid?
Question 2: When I omit the ElementName part, I also get the ToString() representation of CellViewModel. Now I'm really confused. Why is it needed?
Datacontext is a dependency property which is marked as inherits. That means its inherited down the visual tree.
When you bind the default place it's going to look for a source is in the datacontext.
This is the simple situation.
Say you have a window and that has datacontext set to WindowViewmodel and stick a textbox in that Window. You bind it's Text to FooText. This means the textbox goes and looks for a FooText property in that instance of WindowViewmodel.
All pretty simple so far.
Next...
You use elementname.
What that does is says go and take a look at this element. Look for a property on that. If you did that with our textbox above then it would expect a dependency property FooText on whatever you point it to.
Datacontext is a dependency property.
And when you do:
"{Binding FooProperty
This is shorthand for:
"{Binding Path=FooProperty
Where FooProperty is a property path, not =just the name of a property.
Which is maybe worth googling but means you can use "dot notation" to walk down the object graph and grab a property on an object ( on an object.... ).
Hence DataContext.Foo or Tag.Whatever ( since tag is another dependency property a control will have ).
Let's move on to some other complications.
The datcontext is inherited down the visual tree but there's a few of gotchas here. Since
some things look like they're controls but are not ( like datagridtextcolumn ). Templated things can be tricky. Itemscontrols are a kind of obvious and relevent special case.
For an itemscontrol, the datacontext of anything in each row is whichever item it's presented to from the itemssource. Usually you're binding an observablecollection of rowviewmodel to that itemssource. Hence ( kind of obviously ) a listbox or datagrid shows you the data from each rowviewmodel you gave it in each row.
If you then want to go get a property is not in that rowviewmodel you need to tell it to look somewhere else.
When you specify an element in Binding (eg ElementName=SudokuGrid), the Path has to refer to any property of that element. Because this element is a wpf control, DataContext is one of it's properties but ShowPossibilities isn't. So if you do just Path=ShowPossibilities it will not be able to find that path at all.
If you don't specify element in Binding at all then it defaults to the DataContext associated with the control. If the associated DataContext doesn't have the property ShowPossibilities it will not be able to find it.
PS: If you want to debug wpf UI to see what the DataContext is at run-time you could use utility like Snoop.

wpf: changing the value of a Dependency Property from viewmodel

I have a custom usercontrol with a custom dependency property of type string. It should be bound twoway to a textbox, which is in a surrounding app, like this:
<Textbox Name="TextBoxInHostApp"/>
<ctrl:MyControl
....
MyString = "{Binding ElementName=TextBoxInHostApp, Path=Text, Mode=TwoWay}"
</ctrl:MyControl>
Problem:
The Control should have the functionality to reveal the value of the dependency property also!
In MyControl, I have a simple string property "Name", which should update the MyString dependeny property value.
So normally, if MyString was not bound to the outside world, you would code in the xaml where the control is used:
<Textbox Name="TextBoxInHostApp"/>
<ctrl:MyControl
....
MyString = "{Binding Name}"
</ctrl:MyControl>
But that's not available in my case.
Attempt 1:
I used a trigger inside the usercontrol xaml:
<UserControl.Style>
<Style>
<Style.Setters>
<Setter Property="local:MyControl.MyString" Value="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</Style.Setters>
</Style>
Now this trigger doesn't work, because the "local value" of the dp is already set here:
MyString = "{Binding ...
if i comment out:
<!--<MyString = "{Binding ...
...the trigger works fine, because the "local value" isn't set.
next attempt:
I try the hard way and code in MyControls viewmodel:
public string Name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChanged("Name");
MyOwningUICtrl.SetValue(MyControl.MyStringProperty, value); // ??
}
}
I have to pass a DependencyObject to call a SetValue, which is unpleasant for MVVM. There must be better solutions for this scenario ?
This is by design
Per WPF's Dependency Property Precedence List, values specified locally within a <Tag> will always take precedence over values specified in a Style or a Trigger
A common problem is setting a value in a <Tag>, then trying to change it in a Style.Trigger. The solution is to set the default value as a <Setter> in the <Style> instead of in the <Tag>, which will then allow the triggered value to take precedence over the styled value
<Style>
<!-- Default Value
<Setter Property="local:MyControl.MyString"
Value="{Binding ElementName=TextBoxInHostApp, Path=Text, Mode=TwoWay}"/>
<Style.Triggers>
<Trigger ...>
<!-- Triggered Value -->
<Setter Property="local:MyControl.MyString"
Value="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
</Style.Setters>
</Style>
But I don't actually see a Trigger defined in your post anywhere, so I'm not really sure if this is your problem or not.
I'm a bit unclear about the exact structure of your code, but keep in mind that a Dependency Property is kind of like a pointer to another value. You can set it to point to TextBox.Text, or to point to DataContext.Name, but not both.
If you want to set both TextBox.Text and MyUserControl.MyString to point to DataContext.Name, then you should bind both properties to "{Binding Name}" like Natxo suggests.
Or if you are trying to bind MyString to UserControl.Name if MyString is not specified in the <ctrl:MyControl ...> tag, then you can try using a PriorityBinding (example here), or just expose the Name property to the outside world to let outside elements use the control like <ctrl:MyControl Name="{Binding ...}" />
If you display in both the Textbox and Mycontrol the same value... why dont you bind them to that property? :
<Textbox Name="TextBoxInHostApp" Text="{Binding Name}"/>
<ctrl:MyControl
....
MyString="{Binding Name}"
</ctrl:MyControl>

How much cost of using a style for gridview cell that used a converter in it?

I used a style for showing tooltip in a cell of telerik gridview (that converts numbers to words and shows in tooltip) like the below code:
<Window.Resources>
<Style x:Key="PaidAmountConverter" TargetType="telerik:GridViewCell">
<Setter Property="ToolTip" Value="{Binding Path=PaidAmount, Converter={Infrastructure:PriceConverter}}" />
<Setter Property="ToolTipService.Placement" Value="Top" />
</Style>
</Window.Resources>
and use it in gridview like the below code:
<telerik:GridViewDataColumn Header="Paid Amount" DataMemberBinding="{Binding Path=PaidAmount, StringFormat={}{0:N0}}" CellStyle="{StaticResource PaidAmountConverter}" />
my question is how this static resource works? is this style and its converter that used in it, created on time and used for all gridview rows or not?
is this way have performance issue?
if yes, then what is a better way?
There is a single converter object and a single style object. These are resources.
The Grid uses these resources. Even if the bindings were dynamic resources instead of Static resources there would still be just these two objects; static and dynamic refers to how the binding works, not to the life time of the objects.
Make sure the converter code is fast.
For the rest: if you doubt the speed: test.

Silverlight DataBinding, avoid BindingExpression Path error on missing properties, hide controls instead

imagine the following simple Models (example for simplicity reasons; in fact, we have MVVM here but it doesn't matter):
public class User {
public string Username { get; set; }
}
public class StackOverflowUser : User {
public int Reputation { get; set; }
}
Now we have a Silverlight UserControl which contains the following Controls (again, this is just an example, stripped down to the core):
<Grid>
<TextBlock Text="Username:" />
<TextBlock Text="{Binding Path=Username}" />
<TextBlock Text="Reputation:" />
<TextBlock Text="{Binding Path=Reputation}" />
</Grid>
Now I'd like this UserControl to be compatible with both Models, User and StackOverflowUser. I might set the UserControl's DataContext to either a User or StackOverflowUser Type:
this.DataContext = new User { Username = "john.doe" };
If set to StackOverflowUser, everything works fine. If set to User, I'm getting a "BindingExpression Path error", because the Property Reputation is missing in the User Model. Which I understand completely.
Is there any way to 1) avoid this
exception and 2) control the
visibility of the controls, collapse
when bound property is not available?
Of course, we prefer an elegant solution, where the problem is solved by tuning the Binding Expression and/or using Converters etc. and avoid tons of code behind if possible.
Thanks in advance for your help and suggestions,
best regards,
Thomas
Unfortunately Silverlight is limited in its polymorphic behavior regarding DataTemplates, I can only think of a workaround:
Give the User class the property Reputation too, but make it meaningless, for example -1. Then apply a style to the reputation TextBlocks:
<Page.Resources>
<Style Key="Reputation">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Reputation} Value="-1">
<Setter Property="Visibility" Value="Invisible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
...
<TextBlock Text="Reputation:" Style="{StaticResource Reputation}">
<TextBlock Text="{Binding Path=Reputation}" Style="{StaticResource Reputation}">
You could also try (I can not test this):
giving the User class a new property that identifies its type,
make a second Style for the second TextBlock
bind its DataTrigger to the type identifying property and move the {Binding Path=Reputation} declaration into a Setter:
<Style Key="ReputationContent">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type} Value="StackOverflow">
<Setter Property="Visibility" Value="Invisible" />
<Setter Property="Text" Value="{Binding Path=Reputation}" />
</DataTrigger>
</Style.Triggers>
</Style>
But you see, there is no elegant way, it's a shame that DataTemplate do not have a DataType property in Silverlight.
You mentioned you're using MVVM. This is the value of your viewmodel - to shape model data in preparation for the view. The viewmodel could have accessible properties for both username and reputation (and maybe even another bool for binding the visibility). The viewmodel would include all logic on how to fill those properties from either model (User or StackOverflowUser). The view would have no knowledge of a User or StackOverflowUser object, just the viewmodel.
I finally got my problem solved. A co-worker finally implemented a solution including a workaround for WPFs DataTemplates DataType attribute (or generally, a DataTemplateSelector). It's not very pretty (i guess, no workaround is) but it works. Unfortunately, i cannot post any code snippets because its closed-source. But i found some links afterwards, providing a pretty similar solution, like this one: Silverlight: a port of the DataTemplateSelector. If you have a similar problem, this will help you as well. Here or there are more thoughts on this subject.
The actual solution is following Ozan's hints. Unfortunately, his solution is not working so I don't want to mark his comment as the accepted answer but I give at least an upvote.
Thanks!
best regards,
Thomas
I know this has already been answered, but I still think its worth this post. Using reflection you can have a property in your ViewModel that will easily handle Dto objects which only sometimes have the property. Reflection can be expensive though, so weigh that with your decision.
public int? Reputation
{
get
{
var prop = Dto.GetType().GetProperty("Reputation");
return (prop != null)? (int)prop.GetValue(Dto, null) : null;
}
set
{
var prop = Dto.GetType().GetProperty("Reputation");
if(prop !=null) prop.SetValue(Dto,value, null);
}
}

How to expose properties of a WPF DataTemplate?

I have a data template that I use in many pages, the data template contains a few buttons, I want to hide some of these buttons by triggers (I mean setting the IsEnabled Property of these buttons in the page where I use this DataTemplate).
In other words, I would even like to set in style triggers/setters a property 'ButtonXIsEnabled', 'ButtonYIsEnabled' as part of the DataTemplate settable from the ListBox where I use this DataTemplate.
I really hope I am clear enough, please leave comments for any further details.
Any discussion will be really appreciated!
Thanks in advance.
Basically this depends on what object your using for your datatemplate. Instead of using some ButtonYIsEnabled, etcs. Try to use some words that fit better in to your domain model.
For example say you have a list of customers, and some of those customers have the ability to purchase discounted products. Then add a property to your Customer called CanPurchaseDiscountedProducts, and use that property in your DataTemplate
<DataTemplate TargetType="{x:Type local:Customer}">
<!-- Other Items -->
<Button Content="Purchase Discounted Products" x:Name="discounts" Visibility="Hidden" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding CanPurchaseDiscountedProducts}" Value="True">
<Setter TargetName="discounts" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
A WPF data template is a view of a certain object type... how you want an instance of ObjectTypeX to look. The data template can bind to properties on the underlying instance.
So if you have a ButtonXIsEnabled property on your instance, you can bind the corresponding Button's Visibility property to the instance property. The button would be shown or hidden based on the value in the underlying object.

Resources