I'm trying to make textbox's border red when it's empty. Here's my xaml:
<TextBox Style="{StaticResource TextBoxEmptyError}" Name="tbFilename" Grid.Column="1" >
<Binding Path="Text" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<local:EmptyRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
style i'm trying to set:
<Style x:Key="TextBoxEmptyError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
EmptyRule:
public class EmptyRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (string.IsNullOrEmpty(value as string))
return new ValidationResult(false, null);
else
return new ValidationResult(true, null);
}
}
In debugger it looks like Validation method isn't used at all.
What am I doing wrong?
I cannot see where you set DataContext between XAML and viewModel.
DataContext is a way to know where XAML(View, your Window) can get data from.
For example, you have model class:
internal class SomeUser
{
private string _name;
private string _address;
public string Name
{
get { return _name; }
set
{
_name = value;
}
}
public string Address
{
get { return _address; }
set { _address = value; }
}
}
Then you should set DataContext to your Window. For example, in code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new SomeUser();
}
}
then XAML should looks like this:
<Grid>
<Grid.Resources>
<Style x:Key="CustomTextBoxTextStyle" TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="bg" BorderBrush="#FF825E5E" BorderThickness="1">
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Trigger.Setters>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="BorderThickness" TargetName="bg" Value="2"/>
<Setter Property="BorderBrush" TargetName="bg" Value="Red"/>
</Trigger.Setters>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
</Grid>
<TextBox Style="{StaticResource CustomTextBoxTextStyle}" Height="23" Name="textBox1" Margin="25">
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel>
<TextBlock Foreground="Red" DockPanel.Dock="Right">!</TextBlock>
<AdornedElementPlaceholder x:Name="ErrorAdorner"
></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
<TextBox.Text>
<Binding Path="Name" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:NameValidator></local:NameValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
your Binding to Text should be like this...
<Binding Path="Text" UpdateSourceTrigger="LostFocus" Mode="OneWayToSource" NotifyOnValidationError="True" RelativeSource="{RelativeSource Self}">
Related
I am very new to WPF world so do not have much ideas about doing it. Basically,
I want to check whether value entered into textboxes are double number or not.If the value is double number then change the result textbox value to NAN and also change the color of input textbox to red.
Can anyone please guide me how to accomplish this?
Model.cs
public abstract class ObservableBase : INotifyPropertyChanged
{
public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
{
if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue)) && field.Equals(newValue)) return;
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public abstract class ViewModelBase : ObservableBase
{
public bool IsInDesignMode
=> (bool)DesignerProperties.IsInDesignModeProperty
.GetMetadata(typeof(DependencyObject))
.DefaultValue;
}
ViewModel.cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
if (IsInDesignMode)
{
valueA = 2;
valueB = 3;
Calc();
}
}
#region Properties
private int valueA;
public int ValueA
{
get => valueA;
set
{
Set(ref valueA, value);
Calc();
}
}
private int valueB;
public int ValueB
{
get => valueB;
set
{
Set(ref valueB, value);
Calc();
}
}
private int valueC;
public int ValueC
{
get => valueC;
set => Set(ref valueC, value);
}
private int valueD;
public int ValueD
{
get => valueD;
set => Set(ref valueD, value);
}
#endregion
#region Methods
private void Calc()
{
ValueC = valueA + valueB;
ValueD = valueA * valueB;
}
#endregion
}
XAML
<Window x:Class="WPFTestApplication.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:WPFTestApplication.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="TextBox" x:Key="TextBox">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
<Setter Property="Grid.Column" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Silver" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox" x:Key="TextBoxA" BasedOn="{StaticResource TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightBlue" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox" x:Key="TextBoxB" BasedOn="{StaticResource TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightGreen" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource TextBox}"/>
</Grid.Resources>
<TextBlock Text="Value A"/>
<TextBox Text="{Binding ValueA, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxA}"/>
<TextBlock Text="Value B" Grid.Row="1"/>
<TextBox Text="{Binding ValueB, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxB}"
Grid.Row="1"/>
<TextBlock Text="Value C" Grid.Row="2"/>
<TextBox Text="{Binding ValueC}"
IsReadOnly="True"
Grid.Row="2"/>
<TextBlock Text="Value D" Grid.Row="3"/>
<TextBox Text="{Binding ValueD}"
IsReadOnly="True"
Grid.Row="3"/>
</Grid>
</Window>
The easiest way for your problem is to implement a ValidationRule.
Here is the code for the rule:
public class DoubleValidation : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//You can do whatever you want here
double check;
if (!double.TryParse(value.ToString(),out check))
{
//ValidationResult(false,*) => in error
return new ValidationResult(false, "Please enter a number");
}
//ValidationResult(true,*) => is ok
return new ValidationResult(true, null);
}
}
Then in your XAML you have to refer to this ValidationRule when binding, which allows you to get the Validation.HasError property in your style.
<TextBox Validation.ErrorTemplate="{x:Null}">
<TextBox.Text>
<Binding Path="ValueB" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DoubleValidation/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<TextBox.Style>
<Style BasedOn="{StaticResource TextBoxB}" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Since the Error will add a red border to the TextBox, I add Validation.ErrorTemplate="{x:Null}" to keep full control.
If you want to change the Textbox value to NaN, you should do it in your ViewModel. But I would disrecommend that, since it's very boring for the user to see its inputs getting change by the UI.
I have modified your code to achieve your goal:
Model.cs
I have added ObservableBase.NotifyPropertyChanged()
public abstract class ObservableBase : INotifyPropertyChanged
{
public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
{
if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue)) && field.Equals(newValue)) return;
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public abstract class ViewModelBase : ObservableBase
{
public bool IsInDesignMode
=> (bool)DesignerProperties.IsInDesignModeProperty
.GetMetadata(typeof(DependencyObject))
.DefaultValue;
}
Then your ViewModel would look like this, you see I changed the types from int to string, then added validation flags, the trick to check whether the input is double or not is to use double.TryParse.
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
valueAisValid = true;
valueBisValid = true;
if (IsInDesignMode)
{
Calc();
}
}
#region Properties
private string valueA;
public string ValueA
{
get => valueA;
set
{
if (!string.IsNullOrEmpty(value))
{
Set(ref valueA, value);
Set(ref valueAisValid, double.TryParse(ValueA, out double d));
NotifyPropertyChanged(nameof(ValueAIsValid));
Calc();
}
}
}
private bool valueAisValid;
public bool ValueAIsValid => valueAisValid;
private string valueB;
public string ValueB
{
get => valueB;
set
{
if (!string.IsNullOrEmpty(value))
{
Set(ref valueB, value);
Set(ref valueBisValid, double.TryParse(ValueB, out double d));
NotifyPropertyChanged(nameof(ValueBIsValid));
Calc();
}
}
}
private bool valueBisValid;
public bool ValueBIsValid => valueBisValid;
private string valueC;
public string ValueC
{
get => valueC;
set => Set(ref valueC, value);
}
private string valueD;
public string ValueD
{
get => valueD;
set => Set(ref valueD, value);
}
public bool InputsValid => ValueAIsValid && ValueBIsValid;
#endregion
#region Methods
private void Calc()
{
if (InputsValid)
{
double sum = Convert.ToDouble(valueA) + Convert.ToDouble(valueB);
double product = Convert.ToDouble(valueA) * Convert.ToDouble(valueB);
ValueC = sum.ToString(CultureInfo.InvariantCulture);
ValueD = product.ToString(CultureInfo.InvariantCulture);
}
else
{
ValueC = "NAN";
ValueD = "NAN";
}
}
#endregion
}
Now here is the new guy, meet BoolToBackgroundColorConverter.
namespace WPFTestApplication
{
public class BoolToBackgroundColorConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && !(bool)value)
{
return new SolidColorBrush(Colors.Red);
}
else if(value != null && (bool)value && parameter != null)
{
return (SolidColorBrush)parameter;
}
else
{
return new SolidColorBrush(Colors.White);
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Now your xaml would look like:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:BoolToBackgroundColorConverter x:Key="BoolToBackgroundColorConverter"/>
</Window.Resources>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.Resources>
<SolidColorBrush x:Key="LightGreen" Color="LightGreen" />
<SolidColorBrush x:Key="LightBlue" Color="LightBlue" />
<SolidColorBrush x:Key="White" Color="White" />
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="TextBox" x:Key="TextBox">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
<Setter Property="Grid.Column" Value="1"/>
</Style>
<Style TargetType="TextBox" x:Key="TextBoxA" BasedOn="{StaticResource TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding ValueAIsValid, Converter={StaticResource BoolToBackgroundColorConverter}, ConverterParameter={StaticResource LightBlue}}" />
</Trigger>
</Style.Triggers>
<Setter Property="Background" Value="{Binding ValueAIsValid, Converter={StaticResource BoolToBackgroundColorConverter}, ConverterParameter={StaticResource White}}" />
</Style>
<Style TargetType="TextBox" x:Key="TextBoxB" BasedOn="{StaticResource TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding ValueBIsValid, Converter={StaticResource BoolToBackgroundColorConverter}, ConverterParameter={StaticResource LightGreen}}" />
</Trigger>
</Style.Triggers>
<Setter Property="Background" Value="{Binding ValueBIsValid, Converter={StaticResource BoolToBackgroundColorConverter}, ConverterParameter={StaticResource White}}" />
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource TextBox}"/>
</Grid.Resources>
<TextBlock Text="Value A"/>
<TextBox Text="{Binding ValueA, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxA}"/>
<TextBlock Text="Value B" Grid.Row="1"/>
<TextBox Text="{Binding ValueB, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxB}"
Grid.Row="1"/>
<TextBlock Text="Value C" Grid.Row="2"/>
<TextBox Text="{Binding ValueC}"
IsReadOnly="True"
Grid.Row="2"/>
<TextBlock Text="Value D" Grid.Row="3"/>
<TextBox Text="{Binding ValueD}"
IsReadOnly="True"
Grid.Row="3"/>
</Grid>
Output:
Hope this helps!
The easiest way to do this is to handle the validation in the View layer - using a control such as DoubleUpDown from the Extended WPF Toolkit, rather than trying to validate in the ViewModel by parsing a text string.
I am using WPF Datagrid to group an observable collection by Parent. I have been following the example here and other examples showing a parent child relationship. So far I have the following:
And I want to get something like this:
Where the Header of a group is just a row as well but still able to collapse/expand child rows. I have tried to make a sub-datagrid without luck. So the underlying type of my collection looks something like this:
public class Task : INotifyPropertyChanged, IEditableObject
{
// memebers
...
public string ProjectName
{
get { return this.m_ProjectName; }
set
{
if (value != this.m_ProjectName)
{
this.m_ProjectName = value;
NotifyPropertyChanged("ProjectName");
}
}
}
public string TaskName
{
get { return this.m_TaskName; }
set
{
if (value != this.m_TaskName)
{
this.m_TaskName = value;
NotifyPropertyChanged("TaskName");
}
}
}
public DateTime DueDate
{
get { return this.m_DueDate; }
set
{
if (value != this.m_DueDate)
{
this.m_DueDate = value;
NotifyPropertyChanged("DueDate");
}
}
}
public bool Complete
{
get { return this.m_Complete; }
set
{
if (value != this.m_Complete)
{
this.m_Complete = value;
NotifyPropertyChanged("Complete");
}
}
}
public Task Parent
{
get { return m_Parent; }
set
{
m_Parent = value;
}
}
So my object type can either by a child or parent, where a child has a reference to its parent. So I am grouping by parent, I just haven't figured out how to make the expandable group headers rows of the same type. Any help is appreciated.
Here is the xaml:
<DataGrid x:Name="dataGrid1"
ItemsSource="{Binding Source={StaticResource cvsTasks}}"
CanUserAddRows="False"
ColumnWidth="*"
RowHeaderWidth="0">
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel HorizontalAlignment="Stretch" >
<TextBlock FontWeight="Bold" Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Margin="5,0,0,0" Width="200" HorizontalAlignment="Stretch"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}" Width="Auto"/>
</DockPanel>
<!--<DataGrid x:Name="dataGrid2"
ItemsSource="{Binding Source={StaticResource vsParentTasks}}"
CanUserAddRows="False"
ColumnWidth="*"
RowHeaderWidth="0"
HeadersVisibility="None" >
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="#FFEEEEEE" />
<Setter Property="Background" Value="#FF112255" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</DataGrid.RowStyle>
</DataGrid>-->
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="White" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
I'm trying to style a rectangle inside of a listview item, based on a data-field of the listview item object.
To return a boolean I'm converting the integer from daydata.workload to a boolean uasing a IValueConverter.
I'm getting no exception, the rectangle is just not affected by the DataTrigger. The other style rules are working fine.
<Window.Resources>
<cv:numConverter x:Key="capacityConverter" />
<Window.Resources>
-
<ListView Name="weekView" ItemsSource="{Binding dayList}" ItemTemplate="{StaticResource DefaultTemplate}" >
<ListView.Resources>
<Style TargetType="Rectangle" x:Key="capacityBG">
<Setter Property="Stroke" Value="#FFE2E2E2" />
<Setter Property="Width" Value="180" />
<Setter Property="Height" Value="10" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=dayList.workload, Converter={StaticResource capacityConverter}, ConverterParameter=12}">
<DataTrigger.Value>true</DataTrigger.Value>
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
-
<Rectangle Style="{DynamicResource capacityBG}" VerticalAlignment="Top" Grid.Row="0" />
-
public class numConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((int)value) > val;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public int val { get; set; }
}
-
public class dayData
{
public DateTime date { get; set; }
public int workload { get; set; }
public List<job> jobs { get; set; }
}
The problem here is that values entered for the converter parameter and the data trigger value are treated as string. You need to specify the type for each one of these values like shown below:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<Style.Triggers>
<DataTrigger>
<DataTrigger.Binding>
<Binding Path="WorkLoad" Converter="{StaticResource capacityConverter}">
<Binding.ConverterParameter>
<sys:Int32>12</sys:Int32>
</Binding.ConverterParameter>
</Binding>
</DataTrigger.Binding>
<DataTrigger.Value>
<sys:Boolean>true</sys:Boolean>
</DataTrigger.Value>
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
</Style.Triggers>
Then you can cast converter parameter to an int to make the comparison.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value > (int)parameter;
}
At the moment I having working tabs and they open and close properly. I'm trying to implement a Close All functionality and a Close all but this tab functionality, was wondering how abouts do I do that? The Tabs are Initialized in my ShellViewModel.
Current TabsView.xaml
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel Name="Panel" Background="#88DDDDDD" SnapsToDevicePixels="True" Orientation="Horizontal" Margin="1 0" cal:Message.Attach="[Event MouseDown] = [Action Show($dataContext)]">
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" TextAlignment="Right" FontWeight="Bold" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Close All Tabs" cal:Message.Attach="[Event Click] = [Action CloseTabs($this)]"/>
<MenuItem Header="Close All But This" cal:Message.Attach="[Event Click] = [Action CloseAllButThis]"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
<Button Background="Transparent" cal:Message.Attach="[Click] = [Close($this)]" BorderThickness="0" VerticalAlignment="Center">
<ContentControl ContentTemplate="{StaticResource Icons.CloseButtonSmall}" Background="#900" Width="10" Height="10" Margin="3" VerticalAlignment="Center"/>
</Button>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Panel" Property="Background" Value="#DDD"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#093"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
TabsViewModel.cs
private readonly IEventAggregator _events;
public List<IScreen> Items { get; private set; }
public WorkTabsViewModel(IEventAggregator events)
{
_events = events;
_events.Subscribe(this);
}
public void Close(IScreen tab)
{
if (tab.DisplayName == "Settings")
{
var settingsViewModel = tab as SettingsViewModel;
if (settingsViewModel != null)
{
tab.TryClose();
}
}
else
{
tab.TryClose();
}
}
public void Show(IScreen screen)
{
_events.PublishOnUIThread(new ShowTabEvent(screen));
}
public void Handle(ScreenChangeEvent screenChangeEvent)
{
Items = screenChangeEvent.Tabs.Where(x => Array.IndexOf(HiddenTabs, x.GetType().Name) < 0).ToList();
NotifyOfPropertyChange(() => Items);
}
In your ViewModel you can do the following:
public class WorkTabsViewModelpublic extends Conductor<IScreen>.Collection.OneActive
{
// ....
void CloseAll() {
foreach (IScreen tab in Items)
{
tab.TryClose();
}
}
// ....
}
Then in your view you can add a button that will call that method when clicked
<Button x:Name="CloseAll">Close All Tabs</Button>
As long as your ViewModel is extends a Conductor<IScreen>.Collection.OneActive (or something similar), this will loop through all of your open tabs and try to close them.
I came across a situation where I want to avoid using converter in multibinding, below is the xaml source snippet from my current code. Below code works perfectly fine, but IS IT POSSIBLE to avoid converter first place ??
ViewModel:
public MainViewModel()
{
Cars = new List<string>() { "Audi", "BMW", "Ferrari", "Ford" };
Models = new List<string>() { "Model 1", "Model 2" };
IsOptionEnable = false;
}
public bool IsOptionEnable { get; private set; }
public List<string> Models { get; private set; }
public List<string> Cars { get; private set; }
Main window xaml:
<Grid>
<ComboBox HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" Margin="87.2,44.8,0,0"
ItemsSource="{Binding Cars}"
SelectedItem="{Binding SelectedItm}"
Style="{StaticResource ModelsComboBox}">
</ComboBox>
</Grid>
Resource dictionary:
<Style x:Key="ModelsComboBox" TargetType="ComboBox">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding Converter="{StaticResource ModelToBoolConverter}">
<Binding/>
<Binding Path="DataContext.IsOptionEnable" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Multivalue converter:
internal sealed class ModelToBoolConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool enable = true;
if ((values[0] != null && values[0] != DependencyProperty.UnsetValue) &&
(values[1] != null && values[1] != DependencyProperty.UnsetValue))
{
var comboboxItemText = values[0] as string;
if ((comboboxItemText == "Ferrari") && (bool)values[1] == false)
{
enable = false;
}
}
return enable;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
You can use MultiDataTrigger in this case.
<Style TargetType="ComboBox">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding}" Value="Ferrai"/>
<Condition Binding="{Binding Path=DataContext.IsOptionEnable, RelativeSource={RelativeSource AncestorType=ComboBox}}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
You can use MultiDataTrigger to achieve the same.
Resource dictionary:
<Style x:Key="ModelsComboBox" TargetType="ComboBox">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding}" Value="Ferrari"/>
<Condition Binding="{Binding Path=DataContext.IsOptionEnable,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"
Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>