WPF XAML string interpolation - wpf

I'm struggling to achieve string interpolation in Setter of the TextBlock in XAML.
I have a string with a {0} inside and want this "tag" to be replaced by some text. I'd use Multibinding and break my message into parts, but my app should be translated to other languages. For example, I have message like "Das Verzeichnis {0} wurde nicht gefunden" which due to semantics of different languages may look different, for example "The following path could nont been found: {0}". Sure, in this case I still can paraphrase it and then break into 3 parts to give a proper StringFormat to the Multibinding, but there is no guarantee it will work, let's say in Japanese. I also can't use multiple Runs, since Setter only receives one value.
Is there a way to get something similar to string.Format() in XAML?
UPD: Code:
<DataTrigger Binding="{Binding State}" Value="{x:Static vm:StatesEnum.UnknownError}">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Text" Value="{Binding to resource string}" />
</DataTrigger>
UPD2: I wrote a simple converter, that makes all that stuff with string formatting. However, Binding seems to be tricky. XAML code below illustrates my problem. I need to bind to the whole DataContext of it, because State from the DataTrigger (first listing) is not sufficient for the text I want, I need some other fields from the bound object.
<Style x:Key="ImageErrorTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Path=???, Converter={StaticResource ImageToError}}" />
</Style>
However, as I found out, use of converter does not allow use of {Binding} (without Path=some_field). Leaving Path empty is allowed, but in this case Convert is only called once (probably creation or initialization of DataContext). The problem is, that the DataContext is declared in code behind and lies in collection, which is not bound to anything, so I have to apply this converter either in code behind or via setters in style. How can I bind Converter to the {Binding} or maybe there is another way of accessing the whole bound object?

Maybe you can try this :)
<DataTrigger Binding="{Binding State}" Value="{x:Static vm:StatesEnum.UnknownError}">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Text">
<Setter.Value>
<MultiBinding StringFormat="{}{0}{1}">
<Binding Path="" Converter=""/>
<Binding Path="" Converter=""/>
</MultiBinding>
</Setter.Value>
</Setter>
</DataTrigger>

Related

Stucturing a WPF gauge control

I have implemented a gauge control as a custom control in WPF. It is working very well, but I have hit an issue which is causing me to doubt whether it is structured it correctly.
Using different control templates, I can give the gauge radically different styles, for example like a speedometer, a voltmeter, a thermometer or an oil level.
The top level gauge control contains two collections, of Ranges and Pointers.
A Range defines a colored area on the scale. This is a simple object with a few properties.
A Pointer, fairly obviously, defines a pointer on the gauge. A guage can have multiple pointers, and the pointers can have differnt styles, for example a needle, a colored block or a slider (which the user can drag).
This is an example configuration:
<gauge:Gauge MinValue="0"
MaxValue="350"
MajorDivisions="7"
MinorDivisions="5"
Caption="Bar"
Background="White"
LabelOrientation="Horizontal"
BorderThickness="3"
StartAngle="-180"
EndAngle="135"
MinorTickMarkColor="Black"
MajorTickMarkColor="Black"
LabelsOnTicks="Even"
LabelRadiusRelative="0.65"
Template="{StaticResource DefaultRotaryGauge}" >
<gauge:Gauge.Ranges>
<gauge:Range MinValue="200" MaxValue="350" Color="Green"/>
</gauge:Gauge.Ranges>
<gauge:Gauge.Pointers>
<gauge:Pointer Position="{Binding Position1}" ShowNumericValue="Visible" Color="LightGray"/>
<gauge:Pointer Position="{Binding Position2}" ShowNumericValue="Visible" Color="Red"/>
</gauge:Gauge.Pointers>
<gauge:Gauge.LabelStyle>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="10"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
</gauge:Gauge.LabelStyle>
</gauge:Gauge>
As shown above, the pointer position must work with binding, so it must be a dependency property and it must - as far as I understand - be in the visual tree.
For this purpose, the pointer class is defined as a control.
The control template for the gauge uses an ItemsControl to include the pointers. Each pointer has its own control template, but because there are different styes of pointer, I use a trigger to select one of several different templates for a pointer.
For example, my control template for a thermometer supports two pointer styles, which are selected as follows.
<ItemsControl ItemsSource="{TemplateBinding Pointers}"
Grid.Row="2"
Grid.Column="3"
ItemsPanel="{StaticResource GridTemplate}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="local:Pointer">
<Setter Property="Control.Template" Value="{StaticResource ThermometerPointer}"/>
<Style.Triggers>
<Trigger Property="DisplayStyle" Value="Block">
<Setter Property="Control.Template" Value="{StaticResource ThermometerBlock}"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
So far so good.
Now I have had a request, to change the colour of the liquid in the thermometer, when the value passes a given threshold. When I tried to implement this with a DataTrigger,
<gauge:Pointer Position="{Binding Position1}" DisplayStyle="Block">
<gauge:Pointer.Style>
<Style TargetType="gauge:Pointer">
<Setter Property="Color" Value="Red"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource GreaterThanConverter}">
<Binding Path="Position1"/>
<Binding Source="200.0"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Color" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</gauge:Pointer.Style>
</gauge:Pointer>
the pointer stopped working entirely, because it lost the connection to its control template.
I was able to fix it by specifying the Control.Template in addition to the DataTrigger.
<gauge:Pointer Position="{Binding Position1}" DisplayStyle="Block">
<gauge:Pointer.Style>
<Style TargetType="gauge:Pointer">
<Setter Property="Control.Template" Value="{StaticResource ThermometerBlock}"/>
<Setter Property="Color" Value="Red"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource GreaterThanConverter}">
<Binding Path="Position1"/>
<Binding Source="200.0"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Color" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</gauge:Pointer.Style>
</gauge:Pointer>
This works, but I'm not happy with it. The user of the guage control cannot know this detail about the internal impolementation.
I think it is probably bad practice for the control template of the gauge control to define a Style for the pointer. My problem is I can't figure out another way to select different control templates for the pointer.
I thought that I might be able to select a control template using a converter (converting my DisplayStyle property to a ControlTemplate), but I can see where I would apply the convertor.
My only other idea is to get rid of the DisplayStyle property and instead define multiple pointer classes which derive from a common base pointer class. I could then define separate control templates for each derived pointer class.
I have several questions.
Firstly, am I doing this right?
Does it even make sense to define the pointer as a control, with its own control template?
Secondly, is there a way to select the control template for the pointer, without requiring a style definition?
If not, is the idea with derived classes the way to go?

Binding errors on ContentControl after changing Content before ContentTemplate is applied

I'm having an annoying issue that is not exactly causing problems, but it is generating a ton of binding errors unnecessarily.
I've basically tracked the problem down to the fact that setting the Content on a ContentControl changes the DataContext of its content before it applies the new ContentTemplate. Since the new Content is not of the same type as the old ContentTemplate expects, it generates binding errors from the old ContentTemplate.
Here's how I have the ContentControl set up. The Content is bound to the ViewModel for the selected tab, and the ContentTemplate is bound to the DataTemplate with the View for that tab. I used to have it using a ContentTemplateSelector instead of a converter with ContentTemplate, but that had the same issues so I tried this instead.
<ContentControl Content="{Binding SelectedTab, Converter={StaticResource ConfigurationViewModelConverter}}" ContentTemplate="{Binding SelectedTab, Converter={StaticResource ConfigurationTemplateConverter}}"/>
Perhaps I've got this wired up wrong somehow but everything is working perfectly with the exception of the binding errors I get when switching tabs, seemingly due to the Content and ContentTemplate getting briefly out of sync. Thanks in advance for any assistance.
So, I'm almost 2 years late on this, but I had been stuck on this exact same issue for about a day so I figured I'd share. I had 3 types of view models corresponding to 3 different UserControls/DataTemplates. I used a style to fix this
<ContentControl>
<ContentControl.Resources>
<DataTemplate x:Key="fooUc">
<local:UC1 />
</DataTemplate>
<DataTemplate x:Key="barUc">
<local:UC2 />
</DataTemplate>
<DataTemplate x:Key="bazUc">
<local:UC3 />
</DataTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<!-- I'm assuming that SelectedTab is an int -->
<DataTrigger Binding="{Binding SelectedTab}" Value="0">
<Setter Property="Content" Value="{Binding FooVm}" />
<Setter Property="ContentTemplate" Value="{DynamicResource fooUc}" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedTab}" Value="1">
<Setter Property="ContentTemplate" Value="{DynamicResource barUc}" />
<Setter Property="Content" Value="{Binding BarVm}" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedTab}" Value="2">
<Setter Property="ContentTemplate" Value="{DynamicResource bazUc}" />
<Setter Property="Content" Value="{Binding BazVm}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
The crucial thing to notice above is the ordering of my setters. If your view object must be changed before the view model object, then put it before, otherwise flip the ordering. The problem is that one has to be changed before the other, they can't change at the exact same time. If your controls are completely different from one another, then this will still produce errors (different ones though!). This worked for me because BarVm and BazVm have a subset of properties as FooVm.
I had the same problem and I ended up detaching all bindings from the old control before doing the view/viewmodel switch.
Hope this helps someone else with same problem.
public static void ClearAllBindings(DependencyObject obj)
{
if (obj == null)
return;
foreach (var child in LogicalTreeHelper.GetChildren(obj))
{
if (!(child is DependencyObject dp))
continue;
BindingOperations.ClearAllBindings(dp);
ClearAllBindings(dp);
}
}

Is it possible to dynamically create a ResourceKey for a StaticResource? (eg using a Binding)

Suppose the object I'm binding to has a property with a string representing the ResourceKey - how do I get a StaticResource to dynamically acquire it's ResourceKey based on a binding to the underlying object?
I want something equivalent to this
MyProperty="{StaticResource ResourceKey={Binding Path=MyProperty}}"
While this compiles, it will fail complaining it can't find a key of type System.Windows.Data.Binding
I don't need dynamic re-evaluation if the underlying value changes (eg DynamicResource)
I found exactly what I was looking for here:
http://blog.functionalfun.net/2009/12/specifying-resource-keys-using-data_31.html
It's a custom markup extension that behaves like a regular binding but the Path will point to a property that transforms the target into a ResourceKey and the binding will then return the resource!
I've found this extremely useful!
It's also possible to do it in the XAML code
<MyControl.Resources>
<Image x:Key="Image_1" Source="{StaticResource ResourceKey={x:Static img:ImageResourcesKeys.Image_1}}" x:Shared="false"/>
<Image x:Key="Image_2" Source="{StaticResource ResourceKey={x:Static img:ImageResourcesKeys.Image_2}}" x:Shared="false"/>
<Style TargetType="MyProperty">
<Style.Triggers>
<DataTrigger Binding="{Binding ImageType}" Value="Image_1">
<Setter Property="MyProperty" Value="{StaticResource Image_1}" />
</DataTrigger>
<DataTrigger Binding="{Binding ImageType}" Value="Image_2">
<Setter Property="MyProperty" Value="{StaticResource Image_2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</MyControl.Resources>
All you need is a string property ImageType to choose the image you are going to use

Updating style on runtime in wpf

I have a style for a ItemContainer that is based on the Item being contained (With a StyleSelector). It works fine. However on runtime the property might be changed but the style isn't updated instantly.
Is there anyway for me to get it to update as soon as the changes are saved?
Use a DataTrigger and a Converter which returns the Type of an object
For example,
<Style.Triggers>
<DataTrigger Binding="{Binding Converter=ObjectToTypeConverter}"
Value="{x:Type local:Person}">
<Setter Property="ItemTemplate" Value="{Binding PersonTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter=ObjectToTypeConverter}"
Value="{x:Type local:Business}">
<Setter Property="ItemTemplate" Value="{Binding BusinessTemplate}" />
</DataTrigger>
</Style.Triggers>
Use binding. Then you will need to implement INotifyPropertyChanged. The value you are setting should be a property and at the end of the setter, raise the property changed event.
If you give an example of your XAML, I can write it out for you.

Databinding to XML in a DataTrigger in WPF

In a WPF application, I have correctly bound a DataTemplate to an XML node that looks like:
<answer answer="Tree", correct="false" score="10" />
In my application, I have a TextBlock with the answer in it. At first, I want it invisible, but when the correct attribute in the XML file changes to "true", it must become visible.
My DataTemplate is hooked up correctly, because everything else works. For example, if I change the answer attribute in the XML file (just for testing), it changes in my WPF view. But I'm having troubles with the visibility. This is my XAML:
<TextBlock Text="{Binding XPath=#answer}" Visibility="Hidden">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#correct}" Value="true">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I'm guessing the Databinding in the DataTrigger isn't working correctly. Anyone have a clue?
I have run into the same problem with databound ToggleButtons. Try removing the Visibility="False" and replacing it with another DataTrigger that handles the incorrect case.
I think the issue is that the Visibility property is hard-coded. Try setting the Visibility in the style:
<TextBlock Text="{Binding XPath=#answer}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#correct}" Value="true">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Sure, it works if you give a specific else case instead of just false. As in my case, it was {x:Null} and value. So when its value to bind is present, it will be true and TextBlock.Visibilty will be set using setters value and when binding path does not have any value inside it, i.e. null in my case, its simply {x:Null} :)

Resources