Is it possible to set element's own property? - c#

I am trying to set expander IsExpanded property inside DataTrigger with Setter.
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander x:Name="myExpander" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=myExpander, Path=IsKeyboardFocusWithin}" Value="False">
<Setter TargetName="Self" Property="IsExpanded" Value="False" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
The problem is when I write the code like
TargetName="myExpander"
I wanted some keyword like "self" or "." - something that associates the Setter target with its parent's elements and finds it.

I think what you're looking for is this:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander x:Name="myExpander" />
<DataTemplate.Triggers>
<Trigger SourceName="myExpander" Property="IsKeyboardFocusWithin" Value="False">
<Setter TargetName="myExpander" Property="IsExpanded" Value="False" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
Note that I used Trigger with SourceName rather than DataTrigger (although the latter would also work). As for the Setter, you need to set the TargetName="myExpander" in order to set the property of your expander - if you did not specify the TargetName, the setter would attempt to set the property on the DataTemplate itself.
At least that's the theoretical solution. In practice it will not work (or at least not as you expect), because a trigger based on the IsKeyboardFocusWithin is not a good choice for what I think you're trying to achieve. Better choice would be to subscribe to the LostFocus event.

Related

WPF Style DataTrigger with binding to DataContext not working

I have a TextBox with a style that has a DataTrigger which changes the text, like this:
<Grid>
<TextBlock Text="Foo">
<TextBlock.Style>
<Style BasedOn="{StaticResource TextStyle}" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding MyBool}" Value="True">
<Setter Property="Text" Value="Bar"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
But it's not working, the text never changes to "Bar". I have tested using another TextBlock with Text="{Binding MyBool}" and this text changes from "False" to "True". Snoop reveals no errors that I can see and there is nothing in the output.
This question may seem like a duplicate of WPF Trigger binding to MVVM property, but my code does not seem different from the accepted answer there (http://www.thejoyofcode.com/Help_Why_cant_I_use_DataTriggers_with_controls_in_WPF.aspx, section "Using a style") in any relevant way. And using a DataTemplate as suggested in the actual answer seems wrong since I only want this to apply to a single TextBlock, but if it is correct, I'm not sure how to write a DataTemplate for this...
EDIT:
This is what the property I'm binding to looks like:
public bool MyBool
{
get { return _myBool; }
set
{
if (_myBool== value)
return;
_myBool= value;
NotifyPropertyChanged();
}
}
private bool _myBool;
Dependency Properties can be set from many different places; inline, animations, coercion, triggers, etc. As such a Dependency Property Value Precedence list was created and this dictates which changes override which other changes. Because of this order of precedence, we can't use a Trigger to update a property that is explicitly set inline in your XAML. Try this instead:
<Grid>
<TextBlock>
<TextBlock.Style>
<Style BasedOn="{StaticResource TextStyle}" TargetType="TextBlock">
<!-- define your default value here -->
<Setter Property="Text" Value="Foo" />
<Style.Triggers>
<DataTrigger Binding="{Binding MyBool}" Value="True">
<!-- define your triggered value here -->
<Setter Property="Text" Value="Bar" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>

Creating template for indexed property

I have a view model that contains a collection of Item Attributes, which in turn each contain a path to an image. There may be anywhere from 0 .. N item attributes in the collection.
A stackpanel in my view contains three identical image controls. Each image control is bound to the path of an item attribute image:
image control 1 is bound to 1st item attribute's image path (found in ItemAttributes[0].Image)
image control 2 is bound to 2nd item attribute's image path (found in ItemAttributes1.Image)
image control 3 is bound to 3rd item attribute's image path (found in ItemAttributes[2].Image)
If there are more than 3 attributes, they are ignored. In order to deal with the possibility of having 0 - 2 attributes (which means 1 or more of the image controls will be bound to null), which in turn gives the error stated in this post, I have added a data trigger like so:
<DataTrigger Binding="{Binding Attribute1}" Value="{x:Null}">
<Setter Property="Source" Value="{x:Null}"/>
</DataTrigger>
Also, in order to prevent an index out of bound issue, I have split the item attributes in my view model into three properties (i was initially returning String.Empty, but changed it to null to work with the data trigger):
public string Attribute1
{
get { return _item.Attributes.Count > 0 ? _item.Attributes[0].Image : null; }
}
public string Attribute2
{
get { return _item.Attributes.Count > 1 ? _item.Attributes[1].Image : null; }
}
public string Attribute3
{
get { return _item.Attributes.Count > 2 ? _item.Attributes[2].Image : null; }
}
So my problem is that I would like to have this data trigger work for all three image attributes and the corresponding image controls (along with some other properties like width, height, margin etc.). So I think, put it in a style and reference it as a static resource. This won't work when I have three different properties with different names (Attribute1, Attribute2, Attribute3). So now I am stuck doing it this way:
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="{Binding Attribute1}" />
<Setter Property="Width" Value="44" />
<Setter Property="Height" Value="45" />
<Setter Property="Margin" Value="5" />
<Style.Triggers>
<DataTrigger Binding="{Binding Attribute1}" Value="{x:Null}">
<Setter Property="Source" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
This is repeated for the other two image controls, except the Attribute1 is substituted with Attribute2 and Attribute3.
So then I was wondering if there is a way to bind to a collection instead, such as
<DataTrigger Binding="{Binding Attributes}" Value="{x:Null}">
<Setter Property="Source" Value="{x:Null}"/>
</DataTrigger>
... and then specifcy the index I'm interested in the image control binding, outside of the template (i guess like passing a parameter to the data trigger).
Any ideas... is there another approach if this isn't possible?
<ItemsControl ItemsSource="{Binding Attributes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source={Binding Something} x:Name=Image/>
<DataTemplate.Triggers>
<DataTrigger Binding={Binding Something} Value={x:Null}>
<Setter TargetName=Image Property=Source Value={x:Null}/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Typed this manually in the answer, not real XAML. But you should be able to understand what I mean.
Edit: If your Attributes are just strings, use "{Binding}" wherever I placed "{Binding Something}"
--UPDATE (I'll update your answer with what I did since your answer was correct)--
<ItemsControl ItemsSource="{Binding Attributes}"
Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Margin="5">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image x:Name="Image"
Source="{Binding}"
Width="45" Height="44" Margin="5" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter TargetName="Image"
Property="Source" Value="{x:Null}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And to limit it to the first three item attributes, this is what I had in my view model's constructor. Attributes is a SmartObservableCollection property (custom ObservableCollection with and AddRange method and a couple other goodies) with an _attributes backing field:
_attributes = new SmartObservableCollection<string>();
var images = from attributes in _item.Attributes
select attributes.Image;
Attributes.AddRange(images.Take(3));

Why doesn't my DataTrigger update the SelectedIndex property of a TabControl?

I have a TabControl which has its SelectedIndex property bound to a boolean value like this:
<TabControl>
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsRunning, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="SelectedIndex" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabItem Header="Foo" />
<TabItem Header="Bar" />
</TabControl>
The TabControl should only switch to the second tab, if the IsRunningproperty changes to True, but the problem now is, that as soon as the IsRunning property changes, the TabControl does not update itself to display the second TabItem.
Is there a way to do this through XAML, or do I have to implement a SelectedIndex property in my viewmodel, that binds directly to the SelectedIndexof the TabControl?
This works for me just as expected, if the property changes to true the tab switches. Maybe there's a problem with the binding? (Or did i misuderstand the question?)
This is an old thread but who knows someone else may stuble upon this just like me looking for an answer.
Solution: Just add a setter in TabControl style to set SelectedIndex to initial value. e.g. Setter Property="SelectedIndex" Value="0"
<TabControl>
<TabControl.Style>
<Style TargetType="TabControl">
<Setter Property="SelectedIndex" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsRunning, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="SelectedIndex" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabItem Header="Foo" />
<TabItem Header="Bar" />
</TabControl>

WPF DataTemplate Binding depending on the type of a property

I have a collection of objects bound to a hierarchical data template, each of my objects have a property on them (lets call it Property "A") that is of a certain type. This type varies among each of the objects.
If the data template contains an image and some text, what would be the best way to change the image that is displayed in the template based on the type of property "A".
I know I could just stick this into a converter and do the binding translation manually in code, but with all the binding facilities available in WPF, I think theres probably a better way.
It's pretty simple to do this within your data template, if you create local data templates and use a ContentPresenter. This template presents objects of type MyObject, displaying an image whose source is determined by the type of the A property next to a TextBlock that displays the content of the Text property:
<DataTemplate DataType="{x:Type MyObject}">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type Thing1}">
<Image Source="thing1.png"/>
</DataTemplate>
<DataTemplate DataType="{x:Type Thing2}">
<Image Source="thing2.png"/>
</DataTemplate>
</StackPanel.Resources>
<ContentPresenter Content="{Binding A}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
If you want to use styles to do this instead, you're going to run into a problem, because data triggers want to look at property values, and the type of the A property is not, itself, exposed as a property.
Unless, of course, you implement one:
public Type AType { get { return A.GetType(); } }
(You'll also need to raise PropertyChanged for AType when the value of A changes.) Once you've done this, you should be able to implement a data trigger in a style, e.g.:
<Style TargetType="Image">
<Setter Property="Source" Value="default.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding AType}" Value="{x:Type Thing1}">
<Setter Property="Source" Value="thing1.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding AType}" Value="{x:Type Thing2}">
<Setter Property="Source" Value="thing2.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
I think You can do that with triggers.
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding TheProperty}" Value="TheValue">
<Setter Property="Source" Value="NewPath"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
DataTemplateSelector doesn't seem to be a good choice here since you have the same template for all values of A.
Use DataTriggers:
<DataTemplate>
<StackPanel>
<Image x:Name="image" />
<TextBlock>Your text</TextBlock>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=A}" Value="ValueToCheck1">
<DataTrigger.Setters>
<Setter Property="Source" Value="Image1.png" TargetName="image" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=A}" Value="ValueToCheck2">
<DataTrigger.Setters>
<Setter Property="Source" Value="Image2.png" TargetName="image" />
</DataTrigger.Setters>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Haven't tested it, but the idea is like that.

Relative binding in style of control inside ListBoxItem template

My problem is with the following code, with binding the IsAvailable property of the MyListBoxItem class. My current solution:
<ListBox ItemTemplate="{StaticResource myTemplate}">
<ListBox.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type local:MyListBoxItem}">
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
... (more datatemplates)
</ListBox.Resources>
</ListBox>
My question: In my solution the value of IsAvailable "goes through" two bindings. The first one binds the value to the Tag property of the Label and then in the style triggers, a trigger checks its value and sets a property of the Label. When I used Binding="{Binding IsAvailable, RelativeSource={RelativeSource AncestorType={x:Type local:MyListBoxItem}}}" it didn't work, because the Style can't see any ancestor of the Label (or something similar reason), it resulted binding errors (with code 4 or 40 maybe), for each item added to the ListBox.
So finally: can I make the solution more simple, or there is no another (better) one?
An important thing I've forgot to mention, sorry: I put the DataTemplate in the ListBox's resources because I have more templates (they are basically differ, so I can't style them with triggers), which I have to switch between sometimes...
The ItemTemplate will take the type that the ItemsSource is bound to. Therefore you should be able to simply bind to IsAvailable, as the ListBox's item type is MyListBoxItem. Try this:
<ListBox ItemsSource="...">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You'll need to set your ItemsSource property to a Binding to the MyListBoxItem collection.

Categories

Resources