Dynamically set image of each node - wpf

So I have 3 levels of nodes in my TreeView:
A single Root Node (should display Image1.png)
Some second level nodes (should display Image2.png)
Each second level node has some third-level nodes (should display Image3.png)
I'm trying to use a DataTemplate to dynamically assign the display image to each node, depending upon its level. Since level is not as easily available in WPF as it is in WinForms, I simply resorted to using Tag property of TreeViewItems to store their level. Then I wrote this following Style for assigning display images:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Name="img" Width="20" Height="20" Stretch="Fill">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag}" Value="0">
<Setter Property="Source" Value="Icons\Image1.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Tag}" Value="1">
<Setter Property="Source" Value="Icons\Image2.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock VerticalAlignment="Center" Text="{Binding}" Margin="5,0" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Yes, you guessed it; it doesn't work. Can someone please identify where the problem lies? Or am I doing it just the wrong way?

Right now, your {Binding Tag} will try to find the Tag property of the TreeViewItem's DataContext, not the DependencyProperty. And since, i'm guessing, there is no Tag property in the DataContext, it won't work. If you look at your output Windows in VS, you should see binding errors all over the place.
What you need to do is add the relative source to your binding so it looks at the TreeViewItem instead of the DataContext. Here is an example :
<DataTrigger Binding="{Binding Path=Tag, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}" Value="0">

Related

WPF Temp PlaceHolder For 3rd Party Control loading to long

I have a UI taking a long time because of a intensive 3rd Party Control on this View.
So I am trying to solve it by having a Temp Grid when a property of IsStillLoading = true
When IsStillLoading = false the grid gets replaced by the 3rd Party Control
I do this by using DataTemplates
<Style x:Key="ViewLoadingHelper" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsDataLoading}" Value="true">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid x:Name="DummyBeforeLoad" Background="LightGray" >
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsDataLoading}" Value="false">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<myThirdPartyControl/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Grid>
<ContentControl Style="{StaticResource ViewLoadingHelper}"/>
</Grid>
I have 2 questions:
1) Is this the correct approach? Or would you do it differently?
2) Where can I get or what is this blurry placeholder called.
I am currently using a gray grid, but for example when you look at Facebook, hit the refresh on your browser. For a brieve moment you will see a list of dummy/placeholders posts before seeing the real posts. This is the idea I have and whant to implement this.

WPF override property specified on element from style trigger

I have two TextBlock Run elements with different colors (one is explicitly set on the element). I want them both to change color to red when the value Foo is zero, using the same style. Is this possible somehow? I would rather not duplicate the Style. This is what I want to work:
<Style x:Key="ForegroundStyleTrigger" TargetType="Run">
<Style.Triggers>
<DataTrigger Binding="{Binding Foo}" Value="0">
<Setter Property="Foreground" Value="{StaticResource RedBrush}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBlock>
<Run Text="{Binding Foo, Mode=OneWay}"
Style="{StaticResource ForegroundStyleTrigger}"/>
<Run Text="{Binding Bar, Mode=OneWay}"
Foreground="Blue"
Style="{StaticResource ForegroundStyleTrigger}"/>
</TextBlock>
But since the locally defined color (the one defined on the element) takes precedence over style triggers, nothing happens and the text stays blue for that text run.
Question: Can I override a TextBlocks runs color from a style resource?
If not, how can achieve the expected result without duplicating the style resource?
Can I override a TextBlocks runs color from a style resource?
No, you cannot override a local value using a style setter.
If not, how can achieve the expected result without duplicating the style resource?
If you want the Foreground of the second Run element to be Blue by default and Red only if the Foo source property returns "0" you could create another style and base this one on your existing one:
<Style x:Key="ForegroundStyleTrigger" TargetType="Run">
<Style.Triggers>
<DataTrigger Binding="{Binding Foo}" Value="0">
<Setter Property="Foreground" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="BlueByDefault" BasedOn="{StaticResource ForegroundStyleTrigger}" TargetType="Run">
<Setter Property="Foreground" Value="Blue" />
</Style>
<TextBlock>
<Run Text="{Binding Foo, Mode=OneWay}" Style="{StaticResource ForegroundStyleTrigger}"/>
<Run Text="{Binding Bar, Mode=OneWay}" Style="{StaticResource BlueByDefault}"/>
</TextBlock>
But you cannot set the Foreground property to a local value if you want your style setters to apply.
You would use Style inheritance to achieve what you're asking. The trick then is that all properties must be in the Style as local explicitly set values override implicitly set Style values:
<Style x:Key="ForegroundStyleTrigger" TargetType="Run">
<Style.Triggers>
<DataTrigger Binding="{Binding Foo}" Value="0">
<Setter Property="Foreground" Value="{StaticResource RedBrush}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="BlueForegroundStyleTrigger" BasedOn="{StaticResource ForegroundStyleTrigger}" TargetType="Run">
<Setter Property="Foreground" Value="Blue"/>
</Style>
<TextBlock>
<Run Text="{Binding Foo, Mode=OneWay}"
Style="{StaticResource ForegroundStyleTrigger}"/>
<Run Text="{Binding Bar, Mode=OneWay}"
Style="{StaticResource BlueForegroundStyleTrigger}"/>
</TextBlock>

WPF How to attach Popup to simple UIElement like rectangle

It took me hours to figure out the answer to this question, so I thought I would write an FAQ or answer for what I found. (it is based on the following thread Binding Textbox IsFocused to Popup IsOpen plus additional conditions)
I found lots of examples of binding popups to things like toggle buttons and other things that are based on windows chrome and have built in triggers. But in my application I wanted to bind a popup to a simple rectangle with a custom brush fill. I could not find an example on how to have a popup open ans stay open when a user mouses over the rectangle.
So I am posting this question and I will immediately post the answer I found so that hopefully someone else can benefit from it. I will also mark an answer for anyone who can help me understand if stackoverflow allows posts like this, or a better way I could have gone about it.
EDIT 1)
I can't self answer for 8 hours so here is the working code:
the following is a simple example of how to use the popup on a basic UIElement like a rectangle/ellipse/etc...
<Grid HorizontalAlignment="Stretch" Height="Auto">
<Rectangle x:Name="PopupRec"
Grid.Row="0"
Width="20" Height="20"
HorizontalAlignment="Right"
Fill="Gray" Margin="0,0,0,10" />
<Popup x:Name="SortPopup"
PlacementTarget="{Binding ElementName=PopupRec}"
StaysOpen="False"
PopupAnimation="Slide"
AllowsTransparency="True">
<Border Background="White" Padding="15">
<StackPanel Orientation="Vertical">
<Button Command="{Binding MyCommand}" CommandParameter="5">5</Button>
<Button Command="{Binding MyCommand}" CommandParameter="10">10</Button>
<Button Command="{Binding MyCommand}" CommandParameter="15">15</Button>
<Button Command="{Binding MyCommand}" CommandParameter="20">20</Button>
</StackPanel>
</Border>
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PopupRec, Path=IsMouseOver}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SortPopup, Path=IsMouseOver}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
</Popup>
</Grid>
Just paste this inside window/usercontrol/etc...
I'd suggest the following improvement. It would make the Popup Style independent of any element names, and would thus enable you to use it as a default Style by putting it into the Window's or UserControl's Resources.
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMouseOver,
RelativeSource={RelativeSource Self}}"
Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=PlacementTarget.IsMouseOver,
RelativeSource={RelativeSource Self}}"
Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
And please note that a Rectangle is not a "basic UIElement". It's a Shape, which itself is a FrameworkElement.

Change ListView.ItemTemplate on subelement change

let's say we have simple data class:
public class Ex {
public string Prop1 {...} // notify property
public string Prop2 {...} // notify property
}
and an ObservableCollection of objects of this class. I want to have this collection displayed in a ListView with seperated DataTemplated which is distinguished by Ex.Prop2 (if it is null or empty then template01 is used, otherwise template02). This property can be changed in runtime so simple "trick" with ListView.ItemTemplateSelector does not work :(
How to achieve this functionality? Is it possible to achieve it any other way than listening to NotifyPropertyChanged on each object of the collection and than changing manually the template?
Thanks for your help.
Below piece of code which I already have:
<ListView x:Name="lstTerms"
ItemsSource="{Binding Game.Words}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<!-- checks if element is null or its Prop2 is null or empty. If so, uses NullTemplate -->
<ListView.ItemTemplateSelector>
<local:MySelectTemplate
NormalTemplate="{StaticResource NormalItemTemplate}"
NullTemplate="{StaticResource NullItemTemplate}" />
</ListView.ItemTemplateSelector>
</ListView>
Instead of using a TemplateSelector, you can have a single DataTemplate containing two Grids, which switch visibility dependent on the property values.
Here is an example:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="LightBlue" Name="normalGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Prop1}" Value="{x:Null}">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop1}"></TextBlock>
</Grid>
<Grid Background="Green" Name="nullGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=normalGrid, Path=Visibility}" Value="Visible">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop2}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Obviously you could replace the TextBlock elements with UserControls representing your two DataTemplates.
If you want, you can also remove the need for the bulky Styles by binding Grid.Visibility to a property (named, for example, IsVisible) on your ViewModel and using a VisibilityConverter.
I usually just use a ContentControl which changes its ContentTemplate based on a DataTrigger. DataTriggers respond to the value getting changed, while DataTemplateSelectors do not
<Style x:Key="SomeStyleKey" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Prop2}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Prop2}" Value="">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource SomeStyleKey}" />
</DataTemplate>
</ListView.ItemTemplate>
You could also use a Converter that returns String.IsNullOrEmpty(value) if you wanted a single DataTrigger

WPF Xceed datagrid - datatrigger on cell's content makes me lose data on load...however re

I am using the Xceed datagrid for WPF. Today I was trying to change the background of the whole row if one of its column "SA" has the some value or not null. I wrote the following piece of code in XAML with a converter function in code behind:
<xcdg:DataGridControl.Resources>
<Style TargetType="{x:Type xcdg:DataRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource newConverter}, Path=Cells[SA].Content}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</xcdg:DataGridControl.Resources>
To my surprise, as soon as I load the grid for the first time, the data in the SA column is nowhere to be seen. However, once I scroll down a bit, till the point that row which is supposed to have data for the column is not visible, and then when I scroll up again to see that row, I can see the value in that column as well as the background changed.
What am I doing wrong?
Use simple binding and avoid converter/template
<TextBlock Text="{Binding}"></TextBlock>
To fill color in your column use this or the following code:
<xcdg:Column Title="Unit Price" FieldName="UnitPrice" ReadOnly="True">
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<DockPanel LastChildFill="false" x:Name="UnitPrice">
<TextBlock Text="{Binding}"></TextBlock>
<Image x:Name="img" Width="16" Height="16" DockPanel.Dock="Right"
Margin="2,0,0,0" Visibility="Collapsed"
ToolTip="Unit Price is Undefined." VerticalAlignment="Center"
HorizontalAlignment="Left" />
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="0.00">
<Setter TargetName="img" Property="Visibility" Value="Visible" />
<Setter TargetName="UnitPrice" Property="Background" Value="Pink" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>

Resources