WPF / XAML / MVVM - Set state of checkboxes based on conditions - c#

I am a traditional MVC programmer that just started using MVVM and I do not know how I would program the below scenario the MVVM-way. I probably need multi binding, but can someone please help me and write that code for me? I've spend hours trying to achieve this, but I just don't know how to do it...
Btw, I know how to set the values from my settings file in XAML, but don't know how to write the other logic, EG:
IsEnabled="{Binding Source={x:Static p:Settings.Default}, Path=Pref_QuickProcess}"
This is my scenario:
I have a simple preferences screen with two checkboxes:
□ Quick process (value is set from Settings.Default.Pref_QuickProcess)
□ Upload to youtube (value is set from Settings.Default.Pref_UploadToYoutube)
The following conditions apply:
If "Quick process" is true, "Upload to youtube" should always be set to false and must be disabled.
If "Quick process" is false, "Upload to youtube" should be enabled.
These are the only options:
This is my XAML:
<Window x:Class="SchismRecorder.PreferencesWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Preferences" Height="450" Width="800">
<Grid>
<GroupBox Header="Debug settings" HorizontalAlignment="Left" Height="326" Margin="21,20,0,0" VerticalAlignment="Top" Width="733">
<StackPanel>
<CheckBox Content="Quick process" HorizontalAlignment="Left" x:Name="chkQuickProcess" />
<CheckBox Content="Upload to Youtube" HorizontalAlignment="Left" x:Name="chkUploadToYoutube" />
</StackPanel>
</GroupBox>
</Grid>
This is my code behind:
public partial class PreferencesWindow : Window
{
public PreferencesWindow()
{
InitializeComponent();
chkQuickProcess.IsChecked = Settings.Default.Pref_QuickProcess;
chkUploadToYoutube.IsChecked = Settings.Default.Pref_UploadToYoutube;
ConfigureCheckboxes();
chkQuickProcess.Click -= ChkQuickProcess_Click;
chkQuickProcess.Click += ChkQuickProcess_Click;
}
private void ChkQuickProcess_Click(object sender, RoutedEventArgs e)
{
ConfigureCheckboxes();
}
void ConfigureCheckboxes()
{
if (chkQuickProcess.IsChecked.HasValue)
{
var isChecked = chkQuickProcess.IsChecked.Value;
if (isChecked)
{
chkUploadToYoutube.IsChecked = false;
chkUploadToYoutube.IsEnabled = false;
}
else
{
chkUploadToYoutube.IsEnabled = true;
}
}
}
protected override void OnClosing(CancelEventArgs e)
{
Settings.Default.Pref_QuickProcess = chkQuickProcess.IsChecked ?? false;
Settings.Default.Pref_UploadToYoutube = chkUploadToYoutube.IsChecked ?? false;
Settings.Default.Save();
base.OnClosing(e);
}
}
How do I get rid of my code behind, and get the same result in XAML with things like data triggers, converters, multi binding?
Edit: I think I do not necessarily need a viewmodel with setters to implement this logic, and do it with data triggers ? / multi binding ? instead. But maybe that is not possible?

You probably don't need a view model just to set a few properties in the Settings class that have a certain interdependence. The following XAML should do most or perhaps all of what you are describing.
When the first Checkbox is checked, the IsChecked and IsEnabled properties of the second Checkbox are set to false. However, the Settings.Default.Pref_UploadToYoutube property value is not changed. Not sure if this is strictly required.
By default, the second CheckBox's IsChecked property is bound to Pref_UploadToYoutube via a Style Setter. A DataTrigger on the Pref_QuickProcess property replaces the Binding and sets IsChecked and IsEnabled to false.
Also note the new Binding Path syntax for binding to static properties.
<CheckBox Content="Quick process"
IsChecked="{Binding Path=(p:Settings.Default).Pref_QuickProcess}"/>
<CheckBox Content="Upload to Youtube">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="IsChecked"
Value="{Binding Path=(p:Settings.Default).Pref_UploadToYoutube}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(p:Settings.Default).Pref_QuickProcess}"
Value="True">
<Setter Property="IsChecked" Value="False"/>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
You may also simplify the Settings properties binding paths by assigning the Settings.Default instance once to the DataContext of the StackPanel parent of the CheckBoxes:
<StackPanel DataContext="{Binding Path=(p:Settings.Default)}">
<CheckBox Content="Quick process" IsChecked="{Binding Pref_QuickProcess}"/>
<CheckBox Content="Upload to Youtube">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="IsChecked" Value="{Binding Pref_UploadToYoutube}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Pref_QuickProcess}" Value="True">
<Setter Property="IsChecked" Value="False"/>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</StackPanel>

Related

How to use ElementName when binding to UIElement within template?

I am trying to figure out how to get around the issue of using ElementName when binding to a listbox that is in the same template as the item I am trying to bind. When I run the code shown below, I get the following error:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
commandParameter was null.
Additionally, there is no indication that there is any binding error in the Debug output window.
I have tried using the x:Reference method suggested in Binding ElementName inside a DataTemplate but this threw the following error:
System.Windows.Markup.XamlParseException: 'Cannot call MarkupExtension.ProvideValue because of a cyclical dependency. Properties inside a MarkupExtension cannot reference objects that reference the result of the MarkupExtension
My code is shown below:
<ContentControl Grid.Row="0" Grid.RowSpan="2" HorizontalAlignment="Center" Width="Auto"
Height="Auto" VerticalAlignment="Top" Name="AddDeviceContentControl">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ChooseNewDevice}" Value="true">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<ListBox Name="AddDeviceList" ItemsSource="{Binding Source={StaticResource DeviceTypes}}" >
<ie:Interaction.Triggers>
<ie:EventTrigger EventName="SelectionChanged">
<ie:InvokeCommandAction
Command="{Binding AddDeviceCommand}"
CommandParameter="{Binding ElementName=AddDeviceList, Path=SelectedItem}"/>
</ie:EventTrigger>
</ie:Interaction.Triggers>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Note: I am using a content control and template here because I want the list to be invisible unless the ChooseNewDevice bool is true. I chose not to use a popup because I want the listbox SelectedItem to clear when the list is closed, but the popup saves the state. If there is some way to clear the listbox SelectedItem in XAML (in order to follow MVVM) that would also solve my problem.
As suggested by other people here, you should replace your Interaction Trigger in XAML by moving that logic on your ViewModel, triggered by SelectedItem property changed.
Your XAML would become simplier, something like this:
<ListBox ItemsSource="{Binding Source={StaticResource DeviceTypes}}"
SelectedItem="{Binding SelectedItem}">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ChooseNewDevice}" Value="true">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>

How to hide tab control when item source list is empty

I have a WPF .NET Core application with a TabControl bound to an ObservableCollection for the TabItems. I would like the TabControl to be hidden when the ObservableCollection becomes empty, and I would like to display another panel in its place. Then, when an item is added back to the ObservableCollection, I want the alternate panel hidden and the TabControl reshown. How would I accomplish this, hopefully in XAML with as little code-behind as possible? I know I can do it in code-behind.
Below is the key section of the app. I have hidden the TabControl, and included a Border control to represent the Panel that I will show when the TabControl is hidden.
<Window x:Class="TabTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TabTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Border Name="emptyTabPanel" Grid.Row="1" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"
BorderThickness="1,1,1,1" Margin="5,0,5,5" Visibility="Hidden">
</Border>
<TabControl Name="MainTabControl" Visibility="Visible">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Header}" MinWidth="60"/>
<Button BorderThickness="0" Background="Transparent" Height="16" Width="15" Margin="15,2,0,0">
<Image Source="images/close.png" Height="8"/>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
namespace TabTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<TabEntry> tabEntries;
public MainWindow()
{
InitializeComponent();
tabEntries = new ObservableCollection<TabEntry>();
MainTabControl.ItemsSource = tabEntries;
for (int i = 1; i <= 5; i++)
{
tabEntries.Add(new TabEntry { Header = "tab " + i });
}
}
}
public class TabEntry
{
public string Header { get; set; }
}
}
All ItemsControls provide a HasItems property that you can use in a Trigger. In contrast to a DataTrigger on ItemsSource.Count this also works when ItemsSource is not set at all.
<TabControl ...>
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.Style>
...
</TabControl>
You can do it with a DataTriger in a Style. Note that you need to remove Visibility="Visible" or the Setter won't be able to change it.
<TabControl Name="MainTabControl" Background="Red">
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ItemsSource.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<!-- and so on -->
You need a proper view model for this, rather than binding the tabs directly to the collection. That view model would include a HasItems property which you'll bind your TabControl visibility to, and an inverse property - say IsEmpty - which you'll bind the panel's visibility to.
Bind ObservableCollection's events to listen to changes in item count and raise PropertyChanged events for your view model appropriately.

Conditional display of image in c#

I am currently trying to conditionaly display an image. I read quite a bit about valueConverters and triggers, but i strongly believe that there has to be an easier solution for this easy problem.
The XAML:
<Image Source="C:\Users\Niko\Pictures\red.png" IsEnabled="{Binding IsOn}"></Image>
The code behind:
namespace MVVM {
public class Globals
{
int i = 2;
public bool IsOn
{
get
{
if (i == 1 )
return true;
else
return false;
}
}
}
I played around with the integer i to see if the image gets displayed or not. Any advice is greatly apreciated!
Bind the Image's Visibility to IsOn and use the built in BooleanToVisibilityConverter.
<Image Source="C:\Users\Niko\Pictures\red.png" Visibility="{Binding Visibility, Converter={StaticResource BoolToVis}}"/>
Then add the BooleanToVisibilityConverter as a static resource in either the <Window.Resources> for just that window or <Application.Resources> for your whole application.
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
Note that x:Key is the name that you use to reference the converter after StaticResource.
If you don't want to put a Visibility property in your ViewModel and don't want to use converters, you can use a DataTrigger (here I don't have a ViewModel at all, the image is visible if the ToggleButton is checked):
<Image Source="C:\Users\Niko\Pictures\red.png">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Visibility"
Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=tg}"
Value="False">
<Setter Property="Visibility"
Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<ToggleButton Name="tg" Content="Show" HorizontalAlignment="Left" VerticalAlignment="Bottom" />

Set the ItemTemplateSelector of a treeview inside a user control

I have a problem with the user control I created. This control consists of a search textbox and a treeview. The treeview shows different data templates for different node types. So I created the usercontrol with a dependency property of type datatemplate which can be bound when using my control. Inside the control, the treeview binds to the dependency property. But sadly the treeviewtemplate selector doesn't get called.
<UserControl x:Class="yyy.yyy.yyy.UI.UserControls.SearchableTreeView.SearchableTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:behaviours="clr-namespace:yyy.yyy.yyy.UI.Behaviours;assembly=yyy"
mc:Ignorable="d"
x:Name="parent"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel DataContext="{Binding ElementName=parent}">
<TextBox DockPanel.Dock="Top" Margin="5" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"/>
<TreeView DockPanel.Dock="Top" Margin="5" ItemsSource="{Binding TreeViewItems}" ItemTemplateSelector="{Binding TreeViewTemplateSelector}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="behaviours:TreeViewItemBehaviour.IsBroughtIntoViewWhenSelected" Value="true"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<DataTrigger Binding="{Binding Path=IsVisible}" Value="false">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
The code behind with the dependency property looks like that:
public partial class SearchableTreeView : UserControl
{
public SearchableTreeView()
{
InitializeComponent();
}
public static readonly DependencyProperty TreeViewTemplateSelectorProperty = DependencyProperty.Register(
"TreeViewTemplateSelector", typeof (DataTemplateSelector), typeof (SearchableTreeView), new PropertyMetadata(default(DataTemplateSelector)));
public DataTemplateSelector TreeViewTemplateSelector
{
get { return (DataTemplateSelector) GetValue(TreeViewTemplateSelectorProperty); }
set { SetValue(TreeViewTemplateSelectorProperty, value); }
}
}
And the usercontrol is used in a xaml like that:
<searchableTreeView:SearchableTreeView TreeViewTemplateSelector="{StaticResource TreeViewFieldTemplateSelector}"/>
Where the TreeViewFieldTemplateSelector is a class of type datatemplateselector, which allready worked before i startet to create a usercontrol out of the searchable treeview.
Does anybody know what I'm doing wrong? Or is it not possible to bind a datatemplateselector directly to a treeview?
Thanks
Manuel
You are complicating your system by using a DataTemplateSelector. While it is true that these objects were created for this purpose, there is a much easier way to achieve your requirements. Basically, if you declare a DataTemplate for each data type without specifying the x:Key values, then they will be applied implicitly to all objects of the correct type:
<DataTemplate DataType="{x:Type YourPrefix:YourDataType">
...
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:YourOtherDataType">
...
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:SomeOtherDataType">
...
</DataTemplate>
Now, if you put items of these data types into a collection and data bind that to a collection control, then you'll see your various items rendered as expected, but without the complications of the DataTemplateSelector.
UPDATE >>>
Ok, then try this instead... first remove the DataContext="{Binding ElementName=parent}" setting and then add a RelativeSource Binding for the TreeViewTemplateSelector property:
<DockPanel>
<TextBox DockPanel.Dock="Top" Margin="5" Text="{Binding SearchText,
UpdateSourceTrigger=PropertyChanged}"/>
<TreeView DockPanel.Dock="Top" Margin="5" ItemsSource="{Binding TreeViewItems}"
ItemTemplateSelector="{Binding TreeViewTemplateSelector, RelativeSource={
RelativeSource AncestorType={x:Type YourPrefix:SearchableTreeView}}}">
<TreeView.ItemContainerStyle>
...
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>

Change style of default text of Combo Box in XAML

I'm trying to use XAML to make a combo box that is a drop-down list, that has default text already in the box in Italics, and when you click on the drop down to expand the list of options, all of the options will be listed in normal text rather than Italics. When a selection is made, I want the selected option to still be normal rather than Italic, even when it is in the same place as the default text. I'm new to XAML, and I am not sure how to do this, or if it is even possible?
My combo box is for now, as follows, where the default text to be shown is in the property 'Text'. Basically, I want 'Default Text' to be italic, but nothing else.
<ComboBox x:Name="ColumnComboBox" Grid.Column="1" Width="200" Margin="0,2" IsEditable="True" Text="Default Text" FontWeight="Normal" />
Any help is much appreciated.
You need to do a bit more of work to achieve this.
try this
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<ComboBox Name="comboBox1" Margin="40,55,192,225" FontStyle="Italic">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding}" ></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
<Label Name="lbl" Content="{Binding}" ></Label>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter TargetName="lbl" Property="FontStyle" Value="Normal"> </Setter>
<Setter TargetName="lbl" Property="Background" Value="AliceBlue"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="lbl" Property="FontStyle" Value="Italic"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="lbl" Property="FontStyle" Value="Normal"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
</Window>
and here for testing the style from code behind
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<string> observableCollection = new ObservableCollection<string>();
public MainWindow()
{
for (int i = 0; i < 10; i++)
{
observableCollection.Add( "item:"+ i.ToString());
}
InitializeComponent();
comboBox1.ItemsSource = observableCollection;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
}
It is possible, but as you already noticed, default Text and the text shown when an item selected is the same text. That's why it will be tricky to set default text's style different from selected item's text style. The simplest implementation is to listen to ComboBox's selection changed event. When an item selected, change ComboBox's FontStyle to Normal, and when no item selected change it to Italic.
<ComboBox x:Name="ColumnComboBox" SelectionChanged="ColumnComboBox_SelectionChanged" IsEditable="True" Text="Default Text" FontWeight="Italic">
<ComboBoxItem Content="Item 1" FontStyle="Normal"/>
</ComboBox>
private void ColumnComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ColumnComboBox.FontStyle = ColumnComboBox.SelectedItem != null ? FontStyles.Normal : FontStyles.Italic;
}
Or maybe you actually want a watermark behavior for ComboBox. Check this blog post on decent implementation of watermarked ComboBox, downloadable source code available here.

Categories

Resources