I am trying to have a label which can have two different text values. I will display one of the two based on a bool. I am aware that I can create a binding to my viewmodel which could have a property (e.g. LabelText) which can be changed. However I would need to set the text inside the viewmodel in that case and that feels a bit messy.
I am looking for some kind of converter (IValueConverter) that binds a bool on the text property and has two string Parameters. The converter then chooses the right string for the Text of the label. However, to my knowledge, it is not possible to have more than one parameter on a converter?
Any Ideas how to solve this in a clean fashion? Maybe somehow by subclassing Label, but how?
I would more prefer the Trigger on this case than the IValueConverter. Because with trigger you can have your Text inside the view itself. So If you want to change; you don't have to go find the logic outside of that label.
<Label Text="Hello World!">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding IsActive}" Value="false">
<Setter Property="Text" Value="Not Active" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding IsActive}" Value="true">
<Setter Property="Text" Value="Active" />
</DataTrigger>
</Label.Triggers>
</Label>
So here you are binding the Trigger with your Boolean property in your VM. If its True or False whenever it changes it would trig that trigger.
Just to let you know; You can even change other properties too! Lets say If you want to change the Color as well ? So this could be even more better solution for you than to use ValueConverter in this case.
Hope this helps. Let me know If you need more help.
Related
This is basically simple UI that would conditionally render a label with text Triggered - plain in case the ShowContent property of binding is True. (Overly simplified example here but it works and I can see the label toggles).
<Grid>
<Button Click="ChangeState"/>
<ContentView>
<ContentView.Style>
<Style TargetType="ContentView">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowContent}" Value="True" TargetType="ContentView">
<Setter Property="ControlTemplate">
<Setter.Value>
<ControlTemplate>
<Label>Triggered - Plain</Label>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentView.Style>
</ContentView>
</Grid>
There is also a button that would toggle the state of the Grid:
public void ChangeState(object sender, EventArgs e){
this.state = !this.state; // toggle
if(this.state){
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Selected);
} else {
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal);
}
}
Problem
I am not sure how to apply a different style to the label based on selected state.
If you use VisualStateManager, you need to have a name on the Label. I want to apply selected style in a blanket way on all inner labels.
Also, if we target the label with a name, when the Label is not on the UI (due to state ShowContent being False), GoToState fails with null exception as it cannot find the label.
The best solution seems to be using CSS but that does not support defined colors and dynamic resources (AFAIK).
Any idea what to do?
Update: one possible solution is to apply the state change to all inner elements:
private IList<T> FindAllChildren<T>()
where T : IVisualTreeElement
{
return this.GetVisualTreeDescendants()
.Where(e => e is T)
.Cast<T>()
.ToList();
}
private void ApplyState(string state)
{
VisualStateManager.GoToState(this, state);
FindAllChildren<VisualElement>().ForEach(e => VisualStateManager.GoToState(e, state));
}
public void ChangeState(object sender, EventArgs e){
this.state = !this.state; // toggle
if(this.state){
ApplyState(VisualStateManager.CommonStates.Selected);
} else {
ApplyState(VisualStateManager.CommonStates.Normal);
}
}
You still need to create VisualStateGroup styling for the labels and give labels a specific style/class:
<label class="Selectable">...</label>
<Style class="Selectable" TargetType="Label">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup Name="all">
<VisualState Name="Normal">
<VisualState.Setters>
<Setter Property="Label.TextColor" Value="{DynamicResource Normal_Color}"/>
</VisualState.Setters>
</VisualState>
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="Label.TextColor" Value="{StaticResource Selected_Color}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
I recommend that you work around this. Too many bugs, and too different behavior on the different platforms for containers.
Fixing the visual state is one thing. Then you need to fix "IsEnabled" for child problem. After that if you change the visibility, you will notice that on IOS it is doing one thing, on android - another. (You will start losing this visual state from time to time). At some point you will start looking for ways to force the page to redraw itself.
My advice is, for now, give up on this idea. Until those problems are solved. Wasted too many hours trying to make this work for all platforms.
(Some of the issues are 6+ months old, and they keep pushing them to backlog.)
This is me, asking the same thing, a month ago: Pass the VisualState of CollectionView Item VisualElement to its child VisualElements
Edit: So, what work arounds I use.
Besides styles, visual states, data triggers?
ControlTemplates and Messages between ViewModel <-> View.
Control templates are reusable pieces of user interface, and there isn't much you have to do. You can make all VisualElements bind to the same thing, using TemplatedParent as BindingContext of the container.
Messages I use for some sorts of animations (And other special requests). You can in the ViewModel generate a message, that will be handled (or not) by the View. You have very good control over your View, but you do not break MVVM by coupling them.
A Warning: Every work around is parasitic code (you do something the wrong way, because someone else has been doing his job the wrong way). That code sooner or later will have to be deleted/replaced. Mark it with TODO, because it may take huge part of your app, and later it will be hard to find out all usage places. For now test on IOS. It takes much less work to make it work on IOS, then fix Android, than the other way around.
I use this lib : http://winrtxamltoolkit.codeplex.com/
I want to specify the FontSize of the WatermarkText, how can I do that ?
Thanks
You can use the Watermark property instead of the WatermarkText one to put anything you want as your watermark - e.g. a TextBlock with any properties you want. You can also use the WatermarkTextStyle property when using WatermarkText and specify the Style to use with the provided watermark TextBlock - something like:
<xc:WatermarkTextBox
WatermarkText="Type something">
<xc:WatermarkTextBox.WatermarkTextStyle>
<Style
TargetType="TextBlock">
<Setter
Property="FontSize"
Value="18" />
...
You can check the sample to see some options here.
I'm creating a spline designer which requires multiple spline parts.
It contains 2 views (2 UserControls).
The left one is an ItemsControl templated as a Canvas displaying the splines to edit.
The splines parts are UserControls as well.
The right one is a simple ListBox used to select a Spline part.
These two item container are bound to the same ObservableCollection in a ViewModel.
For now, I have a dependencyProperty in the SplinePartVM named IsSelected
What I exactly want to achieve is to modify the DependencyProperty of the SplinePartVM when the SelectedItem is set in the ListBox.
for example, i'd like to do something like this:
<Trigger Property="IsSelected" Value="True">
<Setter Property="{Binding IsSelected}"/>
</Trigger>
because a simple
<ListBox IsSelected="{Binding SelectedItem, Path=IsSelected, Mode=TwoWay}"/>
doesn't work.
I'm a bit lost here...
I found it.
I had to set IsSelected in the style of the ListBoxItem to make it work.
I've been working on checking a value in an Infragistics DataProvider Field and if it's a specific value, change it.
<igDP:Field Name="BeginDate" Label="Begin Date">
<igDP:Field.Settings>
<igDP:FieldSettings>
<igDP:FieldSettings.EditorStyle>
<Style TargetType="{x:Type igEditors:XamDateTimeEditor}">
<Style.Triggers>
<DataTrigger Binding="{Binding BeginDate}" Value="01/01/0001">
<Setter Property="Text" Value=" "/>
</DataTrigger>
</Style.Triggers>
</Style>
</igDP:FieldSettings.EditorStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:Field>
The BeginDate value is of type DateTime. I am trying to check it for being DateTime's min value and, if so, I simply want the field to display a blank.
I have tried XamDateTimeEditor as well as XamTextEditor. With DateTimeEditor, nothing happens. With TextEditor, all values are blanked out.
Would appreciate a nudge in the right direction!
You can do this by changing the Template of the editor to be empty when the value is the minimum value for a DateTime. There are a few changes needed to accomplish this.
Change #1, in the style provided the binding is to BeginDate and this binding is invalid because the DataContext is the DataRecord and not the item from the list that you are binding to. If you check the output window you will see errors like the following:
System.Windows.Data Error: 40 : BindingExpression path error: 'BeginDate' property not found on 'object' ''DataRecord' (HashCode=13078478)'. BindingExpression:Path=BeginDate; DataItem='DataRecord' (HashCode=13078478); target element is 'XamDateTimeEditor' (Name=''); target property is 'NoTarget' (type 'Object')
To resolve this, change the binding to be "DataItem.BeginDate" rather than "BeginDate".
Change #2, modify the Setter to set the Template rather than the Text and set it to an empty ConrolTemplate.
The updated Field definition will be:
<igDP:Field Name="BeginDate">
<igDP:Field.Settings>
<igDP:FieldSettings>
<igDP:FieldSettings.EditorStyle>
<Style TargetType="{x:Type igEditors:XamDateTimeEditor}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.DateOfHire}" Value="01/01/0001">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</igDP:FieldSettings.EditorStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:Field>
This solution will still allow you to edit values if editing is enabled for this field in your grid.
Is there a way to do something like the following?
<Style TargetType="{x:Type: TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="~Complex Binding~" Value="True" DoNotUnset="True">
<Setter Property="IsExpanded" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
What I basically would like is this to be a "No Undo DataTrigger" if you will. When the Value is no longer "True" I don't want it to set "IsExpanded" back to its previous value.
Here is my attempt to do this using enter actions but this also has problems.
<Style TargetType="{x:Type: TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="~Complex Binding~" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(IsExpanded)" Duration="00:00:01" FillBehavior="Stop">
<BooleanKeyFrameCollection>
<DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
</BooleanKeyFrameCollection>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
First this is insanely verbose but secondly this only works for the duration of the BooleanAnimationUsingKeyFrames. If I change the FillBehavior to "HoldEnd" then it looks correct but now the user can no longer un-expand the treeviewitem by clicks (though oddly enough they still can by using the keyboard).
For those who are wondering: yes I'm trying to expand all items in a tree view by binding because I don't want to build a recursive ItemsGenerator.GetItemContainerFromIndex(i) loop. I'd still like to use a similar "No Undo Datatigger" in other areas of my code.
I would bind the ~Complex Binding~ to a bool in the ViewModel which once set to true is always true.
private bool _onceTrueAlwaysTrue = false;
public bool OnceTrueAlwaysTrue
{
get
{
return _onceTrueAlwaysTrue;
}
set
{
if(value)
{
_onceTrueAlwaysTrue = true;
OnPropertyChanged("OnceTrueAlwaysTrue");
}
}
}
then bind this property to the IsEnabled and you should be fine. If you want to reset it simply make a reset method that sets _onceTrueAlwaysTrue = false;
As for the NoUndo datatrigger, as far as I know there is no such thing. You are gonna have to do some kind of work around every time.
The properties changed by triggers are automatically reset to their previous value when the triggered condition is no longer satisfied. Triggers are optimized for transient states which are expected to change and return to original state, such as IsPressed on Button and IsSelected on ListBoxItem.