I would like to make a WPF usercontrol that shows a string when and only when the datacontext == null. I'm using the TargetNullValue attribute in binding to display a custom string when the datacontext is null, and that's working as intended. But when the datacontext is non-null, it just shows the ToString value, which I would like to get rid of.
Of course I could solve this easily by using a valueconverter, but I'd like to find a way to solve this with xaml only. Does anyone know a way to do it?
In case you want TextBlock to be shown only in case binding value is null, you can have trigger in place and set Visibility to Visible when binding value is null and otherwise Collapsed always.
<TextBlock Text="{Binding TargetNullValue=NullValue}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Use a data trigger on {x:Null}. There are many options using styles, data templates etc., depending on taste and needs. For instance:
<DataTemplate x:Key="ShowOnNull">
<TextBlock x:Name="text"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter TargetName="text" Property="Text" Value="your custom string"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
...
<ContentPresenter ContentTemplate="{StaticResource ShowOnNull}"
Content="{Binding ...}"/>
Related
I have this element:
<TextBlock Style="{StaticResource textReplyMessageStyle}">
<Run x:Name="answerMessage" Text="{Binding Message, Mode = OneWay, FallbackValue = ''}" />
</TextBlock>
And I have the need to print in answerMessage either the content of the var Message (thanks to the binding) or an element from the dictionary (thanks to resource reference done by the code below).
answerMessage.SetResourceReference(Run.TextProperty, "Answer_Message_Not_Selected");
The binding is fully working as well as the dictionary reference, but after setting the resource reference once I can not find a way to make the binding work again.
I tried to re-do the binding programmatically but is not working...
The only working workaround I found is to set programmatically the text of answerMessage.
How can I remove the resource reference from the Run element and make the binding work again?
Just to give a bit of context the variable Message contains a number and the resource Answer_Message_Not_Selected
contains the text "Not selected" or a translation in a different language depending on the dictionary active on the program. I have to use dynamic resource reference because the language of the program can be changed on the fly.
Thanks!
A simple way to work this around is to make the output of the TextBlock depends mainly on the Message property.
For example, you can bind the value of Text to Message only if the value of Message is not null, otherwise set it to your resource:
<TextBlock Style="{StaticResource textReplyMessageStyle}">
<Run x:Name="answerMessage" >
<Run.Style>
<Style TargetType="{x:Type Run}">
<Setter Property="Text" Value="{Binding Message, Mode = OneWay, FallbackValue = ''}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Message}" Value="{x:Null}">
<Setter Property="Text" Value="{StaticResource Answer_Message_Not_Selected}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Run.Style>
</Run>
</TextBlock>
Notice that you should set the starting value of Text inside the style because properties set in the control initializer will override any style setters!
Another thing you can do instead of setting the value of Message to null is to add a property AnswerMessageSelected in the view model, to be more explicit when the TextBlock should change its target value:
<TextBlock Style="{StaticResource textReplyMessageStyle}">
<Run x:Name="answerMessage" >
<Run.Style>
<Style TargetType="{x:Type Run}">
<Setter Property="Text" Value="{Binding Message, Mode = OneWay, FallbackValue = ''}" />
<Style.Triggers>
<DataTrigger Binding="{Binding AnswerMessageSelected}" Value="False">
<Setter Property="Text" Value="{StaticResource Answer_Message_Not_Selected}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Run.Style>
</Run>
</TextBlock>
And generally after using this method, it might not be necessary to use x:Name to refer to in the code behind, since it's a better practice to let the ViewModel do all the work and not the code behind the View:
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource textReplyMessageStyle}">
<Setter Property="Text" Value="{Binding Message, Mode = OneWay, FallbackValue = ''}" />
<Style.Triggers>
<DataTrigger Binding="{Binding AnswerMessageSelected}" Value="False">
<Setter Property="Text" Value="{StaticResource Answer_Message_Not_Selected}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
My initial suspicions were correct, thanks to #Jason Tyler #o_w for confirming that.
When you set a reference to a Run (and also for similar elements) you lose the binding property.
The solution to my problem is simply to do the binding again.
Binding b = new Binding();
b.Path = new PropertyPath("Message");
b.Source = this;
BindingOperations.SetBinding(answerMessage, Run.TextProperty, b);
This solution fixes the problem, but it is possible to fix the cause by setting the Answer_Message_Not_Selected resource to the bonded Message element.
Message = Application.Current.Resources["Answer_Message_Not_Selected "].ToString()
If the resource is dynamic, like in my case, you will need some sort of OnChange event observing the resource (or dictionary) to keep the content of Message always updated.
I have a combobox which is bound to an Enum datatype. Right now the combobox binding works fine but when I tried to bind the visibility of a checkbox to the combobox selection, this binding is not working as expected. What I wanted to do was whenever the combobox selection is "Restore", I want a checkbox to be visible. Below is the code that I am using.
<CheckBox.Style>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbOperation, Path=SelectedValue}" Value="Restore">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
I tried changing the Path between SelectedValue, SelectedItem , SelectedValue.TosString() (hopelessly) but I am not getting the checkbox to change its visibility whenever the combobox has "Restore" as its selection. Should I be making any changes in the Enum that I am binding to the Combobox ? If not, what else am I doing wrong?
I'm willing to bet that you've set Visibility on the CheckBox in the XAML:
<CheckBox
Visibility="Collapsed"
>
However, due to the rules of Dependency Property Value Precedence in WPF, that will override anything that happens in the Style. This is by design and it's not a bad idea when you think through all the implications, but it bites everybody who's new to WPF.
It's an easy fix: Just set the starting value in a Setter in the Style. What the Style does, the Style can undo.
<CheckBox
>
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbOperation, Path=SelectedValue}" Value="Restore">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
I want to have a StackPanel who's visibility should be depending on a Combobox selection. Unfortunatly the XAML below does not work.
I found a solution with a new property which will be set on the PropertyChanged event of the Combobox selection, though I would prefer a strict XAML solution for this.
Any hints on how to solve this?
<StackPanel>
<Label>Picture in Picture function</Label>
<ComboBox Name="cbPictureInPicture" ItemsSource="{Binding Path=PictureInPictureCodeList, Mode=OneWay}" DisplayMemberPath="CodeText"
SelectedValuePath="CodeID" SelectedValue="{Binding Path=PictureInPicture, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbPictureInPicture, Path=IsSelected.CodeText}" Value="Yes">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label>Picture in Picture is used</Label>
(...)
</StackPanel>
you may perhaps rewrite the same as
<DataTrigger Binding="{Binding ElementName=cbPictureInPicture, Path=SelectedItem.CodeText}" Value="Yes">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
assuming the combobox is bound to a collection whose item has CodeText property. so SelectedItem.CodeText will point to the same.
additionally it may not be required to set <Setter Property="Visibility" Value="Visible" /> as it is the default value. it does not have any effect in this case just some extra line of code which can be removed.
You can also use a converter and bind directly to the PictureInPicture property:
<StackPanel Visibility="{Binding PictureInPicture, Converter={StaticResource myVisibilityConverter}}"/>
<Label>Picture in Picture is used</Label>
(...)
</StackPanel>
Create flags and pass this flag in stackpanel visibility converter.
On the basis of flag in converter make decision stackpanel visible/hide whatever
Set this flat in comboBox selection change event if selected value as per your requirement.
I'm starting to get a little confused as I delve further into WPF and I feel like this example will help in better understanding things. My requirement is this: I have a ListView that is using a binding to a collection of plain .NET objects, I want to do two things:
1) highlight the cell of a row in the ListView if the value is a certain value - I figure I can use the GridViewColumn.CellTemplate for this and create a DataTemplate with a DataTrigger, however I am becoming confused here - is the DataType for the DataTemplate supposed to be the ListViewItem or is it supposed to be the type of the underlying object itself?
This is a general point of confusion for me in WPF ..not knowing when to type it to the underlying collection object (which I've seen in examples) vs the list-item type itself. Here is my first attempt:
<GridViewColumn Header="Position">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type ListViewItem}">
<TextBlock Text="{Binding Path=PositionCode}"></TextBlock>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding PositionCode}" Value="QB">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="RB">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="WR">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
However, this not surprisingly leads to the error message
Cannot find the Template Property 'Background' on the type 'System.Windows.Controls.ContentPresenter'
2) similar to 1) I want to have a similar rule on another criteria I want to highlight the entire row, instead of just the cell based on a similar DataTrigger property but same time I want the cell highlighting to take precedence over the row highlighting.
How would I do this and what template do I need to override to do this? I'm guessing it's the ListView.ItemTemplate but what would the data type be?
Try this:
<GridViewColumn Header="Position">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type ListViewItem}">
<TextBlock Name="TextBlockName" Text="{Binding Path=PositionCode}"></TextBlock>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding PositionCode}" Value="QB">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="RB">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding PositionCode}" Value="WR">
<Setter TargetName="TextBlockName" Property="Foreground" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
I think dvvrd's answer addresses your first question. For the other part (building the row style), you can use the ItemContainerStyleSelector.
<ListView ItemContainerStyleSelector="{StaticResource Selector}" ...
I wrote a simple implementation like this:
public class RowStyleSelector : StyleSelector
{
public override System.Windows.Style SelectStyle(object item, System.Windows.DependencyObject container)
{
var i = (item as Item);
if (i.I == 0) return (Style)App.Current.Resources["Selected"];
else return (Style)App.Current.Resources["Normal"];
}
}
Then the different styles, along with the selector reference, go in App.xaml:
<Application.Resources>
<res:RowStyleSelector x:Key="Selector" />
<Style x:Key="Selected" TargetType="ListViewItem">
<Setter Property="Background" Value="DarkGray" />
</Style>
<Style x:Key="Normal" TargetType="ListViewItem">
<Setter Property="Background" Value="LightBlue" />
</Style>
</Application.Resources>
This approach effectively sets the background color depending on criteria in your model (the Item class in my example), with the column highlighting still in effect.
I want to change the style of the button on the basis of if else condition when first the wpf application is getting loaded. On application loaded using if, there will be one style of button and in else part, there will be another. How to achieve this using Datatriggers or else using MVVM pattern.
Kindly Suggest?
Thanks
You can use Style.Setters to set default value. For other determined conditions use Style.Triggers. This works like if else.
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=EditorWindow, Path=Category}" Value="R">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
<Style.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</Style.Setters>
</Style>
</TextBlock.Style>
Alternatively, if you want to use DataTriggers, you can use the following:
<Button Command="{Binding SomeButtonCommand}" Content="Click Me!">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NormalButtonMode, Mode=OneWay}" Value="True">
<Setter Property="Content" Value="This Button is in Normal Mode" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=NormalButtonMode, Mode=OneWay}" Value="False">
<Setter Property="Content" Value="This Button is in the Other Mode" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
In this case the ViewModel must expose the boolean property NormalButtonMode. In this example I only set the Content property of the button, but you can list any number of Setters inside the DataTrigger.
You can also put this Style in a resource dictionary and just link it for each button using StaticResource. Just make sure you expose the NormalButtonMode (or whatever) property on each and every ViewModel - maybe put it in a base class.
You should look into Data Templates and a Template Selector. Here is a hastily copy pasted example from my own code, it's not immediately applicable to buttons but I think it should help you along your way.
The following is from the application resources xaml file. I use it to decide which view to use for the ProjectViewModel based on a variable in the ViewModel:
<DataTemplate DataType="{x:Type viewmod:ProjectViewModel}">
<DataTemplate.Resources>
<DataTemplate x:Key="ProjectEditViewTemplate">
<view:ProjectEditView/>
</DataTemplate>
<DataTemplate x:Key="ServiceSelectionViewTemplate">
<view:ServiceSelectionView/>
</DataTemplate>
</DataTemplate.Resources>
<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ProjectViewModelTemplateSelector}" />
</DataTemplate>
The ProjectViewModelTemplateSelector is defined as follows:
public class ProjectViewModelTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is ViewModel.ProjectViewModel)
{
if ((item as ViewModel.ProjectViewModel).EditMode)
{
return element.FindResource("ProjectEditViewTemplate") as DataTemplate;
}
else
{
return element.FindResource("ServiceSelectionViewTemplate") as DataTemplate;
}
}
else
return base.SelectTemplate(item, container);
}
}
}