XAML binding with rules - c#

In XAML I can set TwoWay binding to the local settings using the following
<TextBox
Name="TextXYZ"
Text="{Binding Source={x:Static local:Settings.Default},
Path=TextXYZ,
Mode=TwoWay}" />
<CheckBox Content=""
Name="checkBox1"
IsChecked="{Binding Source={x:Static local:Settings.Default},
Path=checkBox1,
Mode=TwoWay}" />
<CheckBox Content=""
Name="checkBoxSaveSettings"
IsChecked="{Binding Source={x:Static local:Settings.Default},
Path=checkBoxSaveSettings, Mode=TwoWay}" />
Is it possible to introduce rules to the binding in XAML so that if checkBoxSaveSettings.IsChecked=true then controls will have twoway binding but if checkBoxSaveSettings.IsChecked=false then the binding mode is another option?

You can achieve what you want with DataTrigger like so:
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=OneWay}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={x:Static local:Settings.Default}, Path=checkBoxSaveSettings, Mode=OneWay}" Value="True">
<Setter Property="Text" Value="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=TwoWay}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Your method however sounds somewhat confusing for the user as you can change control value but it won't take effect until some other CheckBox it ticked. I would recommend binding IsEnabled to checkBoxSaveSettings.IsChecked like so:
<TextBox
Text="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=TwoWay}"
IsEnabled="{Binding ElementName=checkBoxSaveSettings, Path=IsChecked}"/>

Not directly, but there are options for this. Here's just one. Create a converter on your binding. For the converter parameter, pass in the checkbox checked value.
<TextBox
Name="TextXYZ"
Text="{Binding Source={x:Static local:Settings.Default},
Path=TextXYZ,
Converter={StaticResource foo},
ConverterParameter = {Binding ElementName="checkBoxSaveSettings", Path="IsChecked",
Mode=TwoWay}" />
Then create a converter called "foo" (whatever you want). Inside it, if the parameter is true, you return the value passed in. If the parameter is false, you can return whatever you want, including the Settings.Default.TextXYZ value so nothing changes.
Another possible option is to incorporate a setter on TextXYZ but only apply the passed value to the private _TextXYZ if some other condition is true. That other condition would be bound to the checkbox IsChecked. That's something that should be done in a ViewModel and not an object class, but it would work in either.

Related

Access a previous sibling element using RelativeSource binding (not ElementName) in WPF Application?

I am trying to access a property of a sibling element that comes directly BEFORE the target element. This is something I will have to duplicate several times in my app so I'd rather use something reproduceable (not elementName) if possible.
Any Ideas?
<Groupbox x:Name="GB1">
<Checkbox x:Name="CB1" IsChecked="True"/>
<TextBlock>
<Style TargetType="TextBlock">
<Style.Triggers>
<Data.Trigger Binding RelativeSource={??? (I want this to access the Checkbox CB1 above), Path=IsChecked}" Value="True>
<Setter Property="*Do a Thing if IsChecked=True*" Value="..."/>
</Style.Triggers>
</Style>
</TextBlock>
</Groupbox>
Maybe this example will give you an idea.
<StackPanel>
<TextBlock x:Name="textBlock1" Text="Text" />
<TextBlock x:Name="textBlock2" Text="Text">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}}, Path=Children[0].Text}"/>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
Path=Children[0].Text it will look for the first child element of this ancestor and access its Text property.

DataTrigger on each item's textbox in ItemsControl

I have an ItemsControl that displays a list of Labels & TextBoxes that are used for data input and a button that executes some command when pressed (using the input values):
<DataTemplate x:Key="StringParameterTemplate">
<StackPanel Name="StackPanel_Parameter"
Orientation="Horizontal">
<Label Name="ParameterLabel"
Content="{Binding ParameterLabel}"
HorizontalContentAlignment="Right"
Width="200" />
<TextBox Name="ParameterTextBlock"
Text="{Binding ParameterValue, UpdateSourceTrigger=PropertyChanged}"
Width="300"/>
</StackPanel>
</DataTemplate>
. . .
<!-- Display all parameters -->
<ItemsControl Name="ListView_Parameters"
ItemsSource="{Binding ParameterCollection, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
ItemTemplateSelector="{StaticResource TaskParameterTemplateSelector}"
BorderThickness="0" />
<Button Name="ExecuteTaskButton"
Content="{Binding ButtonLabel}"
Style="{StaticResource ExecuteButtonStyle}"
Command="ExecuteTask">
I would like to create a style that enables/disables the button when ANY of the parameters from ListView_Parameters is empty. Something like this:
<!-- Execute button enable / disable -->
<Style x:Key="ExecuteButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Button.IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ListView_Parameters, Path=ParameterValue}" Value="">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
You can achieve this with a single binding using a converter.
<Button Content="{Binding ButtonLabel}"
IsEnabled="{Binding Path=ItemsSource,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}},
Converter={local:ItemsToBooleanConverter}}" />
Then your converter takes an input of the itemssource (for example a list of objects) - and can return true if all fields you want have values, false otherwise.
The converter is mostly boilerplate, but would look like something this:
public class ItemsToBooleanConverter : MarkupExtension, IValueConverter
... but the important part would like like this, if you were using a list:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var items = value as IList<ParameterList>;
return !(items.Any( <you check here for empty values> );
}
You'll need to be sure your parameter entry fields are bound properly to their sources so that the converter check is current.

RibbonMenuButton Binding item clicked to command

I have a problem with the RibbonMenuButton. Currently I have :
<RibbonMenuButton Label="Meeting" Width="Auto" ToolTipDescription="Display requests on the agenda for the meeting selected" ToolTipTitle="Meeting"
LargeImageSource="pack://application:,,,/Resources/meeting.png"
ItemsSource="{Binding MeetingsAvailable}">
<RibbonMenuButton.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</RibbonMenuButton.ItemTemplate>
</RibbonMenuButton>
My MeetingsAvailable is actually a Dictionary<int, string>. This code is working, the RibbonMenuButtonis well displaying each Value of the dictionnary.
Now I'm trying to get back the Key of the MenuItem which has been clicked. My idea was to use a ICommand in my ViewModel and to bind an event to this command. But I don't really know how to get the event corresponding to clicking an Item in the RibbonMenuButton
Do someone have already did that ?
Thank you in advance.
You can data bind an ICommand to a RibbonMenuButton using the ItemContainerStyle property, like this:
<RibbonMenuButton Label="Meeting" ItemsSource="{Binding MeetingsAvailable}" ... >
<RibbonMenuButton.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</RibbonMenuButton.ItemTemplate>
<RibbonMenuButton.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.NameOfCommand,
RelativeSource={RelativeSource AncestorType={x:Type Views:View}}}" />
<Setter Property="CommandParameter" Value="{Binding Key}" />
</Style>
</RibbonMenuButton.ItemContainerStyle>
</RibbonMenuButton>
You have to create a command in your VM to which you can bind. Then you have to do a binding of the key of your dictionary to the command parameter so you can use it inside your commandfunction. Maybe you have to create an additional Button inside your DataTemplate.

how to apply a style using a converter in wpf c#?

I am developing a system with voice commands that apply to a grid of parameters.
I want to apply a style to the element being edited, so that the user knows where he is vocally ...
MyView.xaml
<telerik:RadNumericUpDown Name={Binding Element[0].ID} Grid.Column="0" Name="Left" MinWidth="15" FontSize="11" Minimum="0" NumberDecimalDigits="0"
BorderThickness="0" Maximum="30"
IsInteger="True" ShowButtons="False" ShowTextBox="True"
HorizontalContentAlignment="Left" Value="{Binding Element[0].Input, Mode=TwoWay, ElementName=InputViewUserControl}" Background="Transparent" Foreground="#FF858585" />
<telerik:RadNumericUpDown Name={Binding Element[1].ID} Grid.Column="0" Name="Left" MinWidth="15" FontSize="11" Minimum="0" NumberDecimalDigits="0"
BorderThickness="0" Maximum="30"
IsInteger="True" ShowButtons="False" ShowTextBox="True"
HorizontalContentAlignment="Left" Value="{Binding Element[1].Input, Mode=TwoWay, ElementName=InputViewUserControl}" Background="Transparent" Foreground="#FF858585" />
<telerik:RadNumericUpDown Name={Binding Element[2].ID} Grid.Column="0" Name="Left" MinWidth="15" FontSize="11" Minimum="0" NumberDecimalDigits="0"
BorderThickness="0" Maximum="30"
IsInteger="True" ShowButtons="False" ShowTextBox="True"
HorizontalContentAlignment="Left" Value="{Binding Element[2].Input, Mode=TwoWay, ElementName=InputViewUserControl}" Background="Transparent" Foreground="#FF858585" />
.....i have 30 elements So...
If the user says: element one, I'd like to apply style to Element[0]
If you have an idea let me know Thanks :)
You just need one Style in a Resources section and then you need to add one bool IsSelected property to your Element class:
public bool IsSelected { get; set; } // Implement INotifyPropertyChanged interface here
<Style TargetType="{x:Type telerik:RadNumericUpDown}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
The Style will colour the Background of the object that has an IsSelected property that is set to True. All you need to do now is to set the IsSelected property to True for the current object and set the previous object's IsSelected value to False.
Note that this Style has no x:Key value... that means that it will be implicitly set on all of your controls without you needing to set the Style on each element manually.

DataTemplate in ControlTemplate not updating Binding

I've created a control with 3 PART_s, one PART_ changes depending on the type bound to it, however values changed within the Control do not update the Binding, it seems to work as OneWay Binding.
Here's part of the code I beleive is relevant:
<DataTemplate x:Key="BooleanDAView" DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="DateTimeDAView" DataType="{x:Type sys:DateTime}">
<extToolkit:DateTimePicker Value="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="Int32DAView" DataType="{x:Type sys:Int32}">
<extToolkit:IntegerUpDown Value="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="StringDAView" DataType="{x:Type sys:String}">
<TextBox Text="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
....
<ContentControl x:Name="PART_Content"
Grid.Row="0" Grid.Column="1"
Margin="{TemplateBinding Padding}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
>
<ContentControl.ContentTemplateSelector>
<controls:TypeBasedDataTemplateSelector>
<controls:TypeBasedDataTemplateSelector.Templates>
<controls:TypedDictionary>
<sys:String x:Key="{x:Type sys:Boolean}">BooleanDAView</sys:String>
<sys:String x:Key="{x:Type sys:DateTime}">DateTimeDAView</sys:String>
<sys:String x:Key="{x:Type sys:Int32}">Int32DAView</sys:String>
<sys:String x:Key="{x:Type sys:String}">StringDAView</sys:String>
</controls:TypedDictionary>
</controls:TypeBasedDataTemplateSelector.Templates>
</controls:TypeBasedDataTemplateSelector>
</ContentControl.ContentTemplateSelector>
</ContentControl>
For Content I've also tried ... RelativeSource={RelativeSource AncestorType=local:DABaseControl} but no change.
If the DataTemplate Binding use "{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" the template doesn't change once set.
Or is there a better way to do this?
Thanks
I just encountered the same problem, I wanted to create a DataTemplate with DataType="{x:Type sys:Boolean} that just had a checkbox. But there were many warning signs along the way telling me this isn't the way it should be done.
At first, the simple binding of {Binding} would throw an exception "Two-way binding requires path or xpath", which was the first warning sign. I changed the binding to {Binding .} which worked (even though this MSDN article clearly states that they're equivalent). That fact that voodoo was helping was the second warning sign. It then displayed correctly and the checked state was according to the boolean value, but when clicking the checkbox (even with UpdateSourceTrigger=PropertyChanged), it refused to update the binding source, no matter what I tried. Using diagnostics:PresentationTraceSources.TraceLevel=High showed that it didn't even try to bind back (third warning sign).
I went ahead and created a simple "box" for the bool value - a class with a single bool property named Value with anINotifyPropertyChanged implementation. I changed the binding to {Binding Value} and now everything worked, including two way binding.
Conclusion: It seems a binding can't update the bound object itself, but only properties of that object (which is why {Binding} throws an exception, but the more explicit {Binding .} suppresses that exception, according to H.B.'s answer). In any case, the approach of creating a ViewModel and creating templates that target it appears to be more than a mere design guideline, but an actual technical requirement.
I've actually never worked with a ContentTemplateSelector, but if I had to hazard a guess I would say either it's not responding to PropertyChanged events on your ContentControl.Content property, or your Content binding is incorrect.
You can easily check if your binding is correct or not by removing the ContentTemplateSelector and seeing if data shows up at all. If it does, your binding is correct. If it doesn't, it's incorrect and you need to fix it.
If the problem is the ContentTemplateSelector, then I would suggest switching to a DataTrigger which determines which ContentTemplate to use based on the Content. This is what I usually do, and it uses a Converter which simply returns typeof(value)
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource StringDAView}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type sys:Boolean">
<Setter Property="ContentTemplate" Value="{StaticResource BooleanDAView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type DateTime">
<Setter Property="ContentTemplate" Value="{StaticResource DateTimeDAView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type sys:Int32">
<Setter Property="ContentTemplate" Value="{StaticResource Int32DAView}" />
</DataTrigger>
</Style.Triggers>
</Style>

Categories