I have the following Style for a TabItem
<Style x:Key="SubStudioTabItem" TargetType="{x:Type TabItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Height="20"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
Margin="10,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ContentSource="Header" />
<Button Grid.Column="1"
x:Name="CloseButton"
Width="15"
Height="15"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DockPanel.Dock="Right"
AttachedCommand:CommandBehavior.Event="Click"
AttachedCommand:CommandBehavior.Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Path=DataContext.CloseWorkspaceCommand}">
...
Now I want to make the visibility of the Button on the TabItem optional which can be used in a DataTemplate like
<DataTemplate x:Key="WorkspaceTemplate">
<TabControl x:Name="tabControl"
IsSynchronizedWithCurrentItem="true"
Style="{StaticResource StudioTabControl}"
ItemsSource="{Binding Workspaces}"
SelectedIndex="{Binding SelectedIndex, NotifyOnSourceUpdated=True, Mode=TwoWay}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem"
BasedOn="{StaticResource SubStudioTabItem}">
<Setter Property="??Button.Visibility??" Value="{Binding Path=Display, Converter={StaticResource BooleanToVisibiltyConverter}}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</DataTemplate>
How can I set the state of the visibility of the button on the TabItem from the DataTemplate?
Thanks for your time.
In that case, I would create an attached property, such as type of Visibility and would make a TemplateBinding in the Style, something like this:
<ControlTemplate TargetType="{x:Type TabItem}">
...
<Button Grid.Column="1"
x:Name="CloseButton"
Visibility="{TemplateBinding local:MyClass.ButtonVisibility}"
...
</Button>
And for TabItem in <ItemContainerStyle> would write this (or somewhere else):
<Style TargetType="TabItem" BasedOn="{StaticResource SubStudioTabItem}">
<Setter Property="local:MyClass.ButtonVisibility" Value="{Binding Path=Display, Converter={StaticResource BooleanToVisibiltyConverter}}"/>
</Style>
Edit:
I created a project that implements this method. In the project tried to follow the MVVM pattern. The structure of the project:
Start in order, in folder AttachedProperties there is an attached property, which is responsible for the appearance of the Button.
The code of ButtonVisibility.cs:
using System;
using System.Windows;
public class ButtonVisibilityPro : DependencyObject
{
public static readonly DependencyProperty ButtonVisibilityProperty;
public static void SetButtonVisibility(DependencyObject DepObject, Visibility value)
{
DepObject.SetValue(ButtonVisibilityProperty, value);
}
public static Visibility GetButtonVisibility(DependencyObject DepObject)
{
return (Visibility)DepObject.GetValue(ButtonVisibilityProperty);
}
static ButtonVisibilityPro()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(Visibility.Collapsed);
ButtonVisibilityProperty = DependencyProperty.RegisterAttached("ButtonVisibility",
typeof(Visibility),
typeof(ButtonVisibilityPro),
MyPropertyMetadata);
}
}
The data model is ButtonModel, wherein the boolean property is ButtonDisplay. This class inherits from the ViewModelBase, that implements INotifyPropertyChanged.
ButtonModel.cs
using System;
using ButtonVisibilityHelp.ViewModels;
namespace ButtonVisibilityHelp.Models
{
public class ButtonModel : ViewModelBase
{
private bool _buttonDisplay = false;
public bool ButtonDisplay
{
get
{
return _buttonDisplay;
}
set
{
_buttonDisplay = value;
NotifyPropertyChanged("ButtonDisplay");
}
}
}
}
ViewModelBase.cs
using System;
using System.ComponentModel;
namespace ButtonVisibilityHelp.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
ButtonViewModel.cs
using System;
using System.Windows.Input;
using System.Windows;
using ButtonVisibilityHelp.Models;
using ButtonVisibilityHelp.Workers;
namespace ButtonVisibilityHelp.ViewModels
{
public class ButtonViewModel : ViewModelBase
{
private ButtonModel _buttonModel;
private ICommand _hideButtonCommand = null;
private ICommand _showButtonCommand = null;
public ButtonModel ButtonModel
{
get
{
return _buttonModel;
}
set
{
_buttonModel = value;
NotifyPropertyChanged("ButtonModel");
}
}
public ICommand HideButtonCommand
{
get
{
if (_hideButtonCommand == null)
{
_hideButtonCommand = new RelayCommand(param => this.HideButton(), null);
}
return _hideButtonCommand;
}
}
public ICommand ShowButtonCommand
{
get
{
if (_showButtonCommand == null)
{
_showButtonCommand = new RelayCommand(param => this.ShowButton(), null);
}
return _showButtonCommand;
}
}
public ButtonViewModel()
{
ButtonModel = new ButtonModel();
}
private void HideButton()
{
ButtonModel.ButtonDisplay = false;
}
private void ShowButton()
{
ButtonModel.ButtonDisplay = true;
}
}
}
RelayCommand.cs
using System;
using System.Windows.Input;
namespace ButtonVisibilityHelp.Workers
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
Below is a piece of XAML code that is in the style of TabItem:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" SnapsToDevicePixels="True" Name="Border" Margin="0,0,2,0" Padding="2" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="0">
<ContentPresenter Name="ContentSite"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="5,5,5,5"
VerticalAlignment="Center"
RecognizesAccessKey="True"
ContentSource="Header" />
</Border>
<!-- Here TemplateBinding for Visibility -->
<Button Name="CloseButton" Style="{StaticResource CloseButton}"
Visibility="{TemplateBinding AttachedProperties:ButtonVisibilityPro.ButtonVisibility}"
Grid.Column="1" Width="14" Height="14" HorizontalAlignment="Center" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="TabStripPlacement" Value="Bottom">
<Setter TargetName="Border" Property="CornerRadius" Value="0,0,0,0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
XAML of MainWindow:
<Window x:Class="ButtonVisibilityHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:ButtonVisibilityHelp.ViewModels"
xmlns:AttachedProperties="clr-namespace:ButtonVisibilityHelp.AttachedProperties"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ViewModels:ButtonViewModel x:Key="MyButtonViewModel" />
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource MyButtonViewModel}}">
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="AttachedProperties:ButtonVisibilityPro.ButtonVisibility" Value="{Binding Path=ButtonModel.ButtonDisplay, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Test1">
<StackPanel>
<TextBlock Text="{Binding ButtonModel.ButtonDisplay, StringFormat=ButtonDisplay: {0}, Mode=TwoWay}" HorizontalAlignment="Right" />
<Button Name="ShowInTest1" Command="{Binding ShowButtonCommand}" Width="100" Height="30" Content="Show me" HorizontalAlignment="Right" />
<Button Name="HideInTest1" Command="{Binding HideButtonCommand}" Width="100" Height="30" Content="Hide me" HorizontalAlignment="Right" />
</StackPanel>
</TabItem>
<TabItem Header="Test2">
<StackPanel>
<TextBlock Text="{Binding ButtonModel.ButtonDisplay, StringFormat=ButtonDisplay: {0}, Mode=TwoWay}" HorizontalAlignment="Right" />
<Button Name="ShowInTest2" Command="{Binding ShowButtonCommand}" Width="100" Height="30" Content="Show me" HorizontalAlignment="Right" />
<Button Name="HideInTest2" Command="{Binding HideButtonCommand}" Width="100" Height="30" Content="Hide me" HorizontalAlignment="Right" />
</StackPanel>
</TabItem>
</TabControl>
</Grid>
</Window>
Output
Show me:
Hide me:
The complete code example is available at this link.
Set the Tag property on your TabItem in style like below :
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem"
BasedOn="{StaticResource SubStudioTabItem}">
<Setter Property="Tag" Value="{Binding IsVisible, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</Style>
</TabControl.ItemContainerStyle>
And then in ControlTemplate add DataTrigger to set the Visibility of CloseButton depending on Tag property
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Tag}", Value="false">
<Setter Property="Visibility" TargetName="CloseButton" Value="Collapsed"/>
</DataTrigger>
</ControlTemplate.Triggers>
Related
I need to change the view of a TabControl's content on-the-fly.
I am guessing the best way to accomplish this is to define the view as a DataTemplate, and then change said template using a trigger.
In my test app, the background color is tied to the same data trigger as the template. The background color updates immediately upon making the radio button selection.
Expected behavior: The Tab Item Content / DataTemplate also updates immediately.
Actual Behavior: Tab content view does not update until the tab selection is changed.
Here's my Minimal, Complete, and Verifiable example:
Window XAML
<Window x:Class="ChangeView.Window1"
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"
Title="Window1" Height="350" Width="400">
<Window.Resources>
<DataTemplate x:Key="ContentTemplate1">
<Grid>
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding MyBlurb}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ContentTemplate2">
<Grid>
<Label HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Content="{Binding MyHeader}" Background="Black" Foreground="White" FontSize="72"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType1}" Value="False">
<Setter Property="Background" Value="Chartreuse"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType1}" Value="True">
<Setter Property="Background" Value="Bisque"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Margin="10,38,0,0" Text="Content Template:"/>
<RadioButton x:Name="radio1" Margin="120,40,0,0" Grid.ColumnSpan="2" Content="1" GroupName="ViewSelect" IsChecked="{Binding Path=ViewType1}"/>
<RadioButton Margin="170,40,0,0" Grid.ColumnSpan="2" Content="2" GroupName="ViewSelect"/>
<TabControl Grid.Row="1" ItemsSource="{Binding TabGroup}">
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Margin" Value="10"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType1}" Value="True">
<Setter Property="ContentTemplate" Value="{DynamicResource ContentTemplate1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType1}" Value="False">
<Setter Property="ContentTemplate" Value="{DynamicResource ContentTemplate2}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<DataTemplate>
<Border x:Name="headerBorder">
<Label Content="{Binding MyHeader}" FontSize="20"/>
</Border>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
Code Behind
namespace ChangeView
{
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window, INotifyPropertyChanged
{
public ObservableCollection<TabData> TabGroup { get; set; } = new ObservableCollection<TabData>();
private bool _viewType1 = true;
public bool ViewType1
{
get { return _viewType1; }
set { _viewType1 = value; RaisePropertyChanged(nameof(ViewType1)); }
}
public Window1()
{
TabGroup.Add(new TabData("♻️", "Recycle"));
TabGroup.Add(new TabData("⚔", "Swords"));
TabGroup.Add(new TabData("⚗", "Chemistry"));
TabGroup.Add(new TabData("🌵", "Cactus"));
TabGroup.Add(new TabData("👺", "Tengu"));
TabGroup.Add(new TabData("🐙", "Octopus"));
DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class TabData : INotifyPropertyChanged
{
private string _myHeader, _myBlurb;
public TabData(string header, string blurb)
{
MyHeader = header;
MyBlurb = blurb;
}
public string MyHeader
{
get { return _myHeader; }
set { _myHeader = value; RaisePropertyChanged(nameof(MyHeader)); }
}
public string MyBlurb
{
get { return _myBlurb; }
set { _myBlurb = value; RaisePropertyChanged(nameof(MyBlurb)); }
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
After changing the radio button state, change the selected tab. You will then see the correct content template.
It looks as if, in a TabControl, changing the content template alone does not cause the content to be rendered. If you render new content by switching the selected tab, the current content template will then be used.
So let's write one ContentTemplate, which creates a ContentControl and switches the ContentControl's ContentTemplate. I've tested, and the ContentControl will re-render its content when its ContentTemplate changes. The bindings get a little bit verbose.
<TabControl ItemsSource="{Binding TabGroup}" Grid.Row="1">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl
x:Name="ContentCtl"
Content="{Binding}"
/>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding DataContext.ViewType1, RelativeSource={RelativeSource AncestorType=TabControl}}"
Value="True">
<Setter
TargetName="ContentCtl"
Property="ContentTemplate"
Value="{DynamicResource ContentTemplate1}"
/>
</DataTrigger>
<DataTrigger
Binding="{Binding DataContext.ViewType1, RelativeSource={RelativeSource AncestorType=TabControl}}"
Value="False"
>
<Setter
TargetName="ContentCtl"
Property="ContentTemplate"
Value="{DynamicResource ContentTemplate2}"
/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<Border x:Name="headerBorder">
<Label Content="{Binding MyHeader}" FontSize="20"/>
</Border>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
You could also do something ugly in your code behind to make the TabControl render itself again on command. Or maybe you can replace the metadata on TabControl.ContentTemplate.
I'm trying to change the background of a button, if a property of the data binding is true.
To give a short overview - I have a ListBox. I also gave this ListBox a style for the items:
<ListBox Margin="0,5,0,0" Background="Transparent"
BorderThickness="0"
ItemsSource="{Binding PaymentsAndCollectionData.PaymentsToCreditors}"
SelectedItem="{Binding PaymentsAndCollectionData.SelectedPaymentToCreditor}">
<ListBox.ItemContainerStyle>
.......
</ListBox.ItemContainerStyle>
</ListBox
Now, I want to put a button in this style. So, I used a ControlTemplate like so (I know this looks weird, and I could propably have done this differently!):
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Button Name="Button" HorizontalContentAlignment="Stretch" Background="Transparent" BorderThickness="0"
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},
Path=DataContext.PaymentsAndCollectionData.SelectPaymentToCreditorCommand}"
CommandParameter="{Binding}">
<DockPanel Name="DP" LastChildFill="False" Background="{TemplateBinding Background}">
<TextBlock DockPanel.Dock="Left" Text="{Binding Name}" Margin="20,0,10,0" Padding="0,5,0,5" Foreground="{TemplateBinding Foreground}"/>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
<TextBlock Text="R" VerticalAlignment="Center" Foreground="{TemplateBinding Foreground}"/>
<TextBlock Text="{Binding Amount, StringFormat=N2}" VerticalAlignment="Center" Margin="0,0,10,0" Foreground="{TemplateBinding Foreground}"/>
</StackPanel>
</DockPanel>
</Button>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="DP" Property="Background" Value="{StaticResource APPLICATION_GREEN_COLOR}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So, the above works properly. The Name and Amount is set from the DataBinding. I am just failing to set the background, if IsSelected is true.
My binding object looks like this:
public class PaymentItem
{
public string Name { get; set; }
public decimal Amount { get; set; }
public bool IsSelected { get; set; }
}
If you change the IsSelected property after the object is created, you need to implement INotifyPropertyChanged interface in this object to make the Binding work.
public class PaymentItem : INotifyPropertyChanged
{
private bool _isSelected;
public string Name { get; set; }
public decimal Amount { get; set; }
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a combobox with items from 0 to 63 integer values. I want to add select all option. How can i do that to work if i select all it should select all the items?
<DataTemplate x:Key="cmbIndex">
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
Content="{Binding Name}" Name="chkbox"
Click="CheckBox_Click">
</CheckBox>
</DataTemplate>
<CollectionViewSource x:Key="coll" Source="{Binding Set2CmdList,UpdateSourceTrigger=PropertyChanged}"/>
<ComboBox Grid.Row="0" SelectedIndex="{Binding Set2SelectedIndex}"
HorizontalAlignment="Left" Margin="80,0,0,0"
Height="20" VerticalAlignment="Center" Width="60"
FontFamily="Calibri" FontSize="12" >
<ComboBox.ItemsSource>
<CompositeCollection>
<!--<ComboBoxItem>
<CheckBox x:Name="all">Select All</CheckBox>
</ComboBoxItem>-->
<CollectionContainer Collection="{Binding Source={StaticResource coll}}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate" Value="{StaticResource cmbIndex}"/>
</Style>
</ComboBox.Style>
</ComboBox>
View model:
private List<GenericDescription> _Set2CmdList;
public List<GenericDescription> Set2CmdList
{
get { return _Set2CmdList; }
set { _Set2CmdList = value; }
}
Constructor of viewmodel:
_CMDCollection = new ObservableCollection<int>();
_Set2CmdList = new List<GenericDescription>();
_Set2CmdList.Add(new GenericDescription() { Name="Select All",IsSelected=false });
for (int i = 0; i < 64; i++)
{
_CMDCollection.Add(i);
_Set2CmdList.Add(new GenericDescription()
{
Name = i.ToString(),
IsSelected = false
});
}
Class:
public class GenericDescription
{
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value;}
}
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set { _IsSelected = value; }
}
}
you can try with below control,
Note, this is not a completed control, it will give you a headstart, (you need to check for NRE and other scenarios)
public class MultiComboBox : ComboBox
{
static MultiComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiComboBox), new FrameworkPropertyMetadata(typeof(MultiComboBox)));
EventManager.RegisterClassHandler(typeof(MultiComboBox), Selector.SelectedEvent, new RoutedEventHandler(OnSelected));
EventManager.RegisterClassHandler(typeof(MultiComboBox), Selector.UnselectedEvent, new RoutedEventHandler(OnUnselected));
}
CheckBox PART_SelectAll;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
PART_SelectAll = base.GetTemplateChild("PART_SelectAll") as CheckBox;
PART_SelectAll.Checked += PART_SelectAll_Checked;
PART_SelectAll.Unchecked += PART_SelectAll_UnChecked;
}
private void PART_SelectAll_Checked(object sender, RoutedEventArgs e)
{
ProcessSelection(PART_SelectAll.IsChecked.Value);
}
private void PART_SelectAll_UnChecked(object sender, RoutedEventArgs e)
{
ProcessSelection(PART_SelectAll.IsChecked.Value);
}
internal void NotifySelectedItems(object item, bool isSelected)
{
if (SelectedItems == null)
SelectedItems = new List<Object>();
if (SelectedItems != null)
{
if (isSelected)
SelectedItems.Add((item as MultiComboBoxItem).DataContext);
else
SelectedItems.Remove((item as MultiComboBoxItem).DataContext);
}
}
internal void SetSelectedItem(object item)
{
SetValue(SelectedItemProperty, item);
}
private void ProcessSelection(bool select)
{
foreach (var item in this.Items)
{
if(this.ItemsSource != null)
{
var cItem = this.ItemContainerGenerator.ContainerFromItem(item) as MultiComboBoxItem;
if(cItem != null)
{
cItem.SetValue(ComboBoxItem.IsSelectedProperty, select);
}
}
}
}
private static void OnSelected(object sender, RoutedEventArgs e)
{
e.Handled = true;
}
private static void OnUnselected(object sender, RoutedEventArgs e)
{
e.Handled = true;
}
public MultiComboBox()
{
}
public IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItems. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiComboBox), new PropertyMetadata(null));
protected override DependencyObject GetContainerForItemOverride()
{
var multiComboItem = new MultiComboBoxItem();
multiComboItem.ParentComboBox = this;
return multiComboItem;
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
}
}
public class MultiComboBoxItem : ComboBoxItem
{
static MultiComboBoxItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiComboBoxItem), new FrameworkPropertyMetadata(typeof(MultiComboBoxItem)));
}
public MultiComboBox ParentComboBox { get; set; }
protected override void OnSelected(RoutedEventArgs e)
{
ParentComboBox.NotifySelectedItems(this, true);
base.OnSelected(e);
if (ParentComboBox.SelectedItem == null)
ParentComboBox.SetValue(ComboBox.SelectedItemProperty, this.DataContext);
}
protected override void OnUnselected(RoutedEventArgs e)
{
ParentComboBox.NotifySelectedItems(this, false);
base.OnUnselected(e);
if (ParentComboBox.SelectedItems.Count == 0 || this.DataContext == ParentComboBox.SelectedItem)
ParentComboBox.ClearValue(ComboBox.SelectedItemProperty);
}
}
and in generic file, add template for ComboBox and replace the below lines,
<ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type local:MultiComboBox}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" />
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup"
Grid.ColumnSpan="2"
Margin="1"
AllowsTransparency="true"
IsOpen="{Binding IsDropDownOpen,
Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Placement="Bottom"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}">
<Border x:Name="dropDownBorder"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<CheckBox Grid.Row="0" Content="Select All" Name="PART_SelectAll"/>
<ScrollViewer Grid.Row="1" x:Name="DropDownScrollViewer">
<Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
<Canvas x:Name="canvas"
Width="0"
Height="0"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<Rectangle x:Name="opaqueRect"
Width="{Binding ActualWidth,
ElementName=dropDownBorder}"
Height="{Binding ActualHeight,
ElementName=dropDownBorder}"
Fill="{Binding Background,
ElementName=dropDownBorder}" />
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter"
KeyboardNavigation.DirectionalNavigation="Contained"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Grid>
</ScrollViewer>
</Grid>
</Border>
</Popup>
<ToggleButton x:Name="toggleButton"
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
IsChecked="{Binding IsDropDownOpen,
Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ComboBoxToggleButton}" />
<ContentPresenter x:Name="contentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectionBoxItem}"
ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
IsHitTestVisible="false"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="PART_Popup" Property="HasDropShadow" Value="true" />
<Trigger Property="HasItems" Value="false">
<Setter TargetName="dropDownBorder" Property="Height" Value="95" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true" />
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
</MultiTrigger>
<Trigger SourceName="DropDownScrollViewer" Property="ScrollViewer.CanContentScroll" Value="false">
<Setter TargetName="opaqueRect" Property="Canvas.Top" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}" />
<Setter TargetName="opaqueRect" Property="Canvas.Left" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
A method like this will assign all your item to Selected
Bind this check All method as Command to your checkbox.
public void CheckAll()
{
foreach(var item in Set2CmdList)
{
item.IsSelected = true;
}
}
Class
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged(); //See interface INotifyProeprtyChanged interface
}
}
I've builded an application in WPF (MVVM) and adding validation. Here's my result:
You'll notice the red squires around the input textboxes. The thing is I don't want to confront the client with validation error when starting the form. When entering data or pressing Submit would be best.
There are some similar questions on SO but haven't found a suitable solution.
(Resetting every control on initialize is no solution)
So the question is how startup the form and:
Option A: (easily) Reset the validation
Option B: Don't call validation until after binding
Option C: Other nice solution
Download code:
here
View code:
<Window x:Class="Validation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150" Width="250">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Name -->
<Label Grid.Column="0" Grid.Row="0">Name</Label>
<TextBox Grid.Column="1" Grid.Row="0"
Text="{Binding Model.Name, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"></TextBox>
<!-- Age -->
<Label Grid.Column="0" Grid.Row="1">Age</Label>
<TextBox Grid.Column="1" Grid.Row="1"
Text="{Binding Model.Age, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"></TextBox>
<!-- Submit/Cancel -->
<StackPanel Grid.Column="1" Grid.Row="2" FlowDirection="LeftToRight">
<Button>Cancel</Button>
<Button>Submit</Button>
</StackPanel>
</Grid>
</Window>
ViewModel code:
public class FormViewModel
{
#region constructors
public FormViewModel()
{
Model = new FormModel();
}
#endregion
#region properties
public FormModel Model { get; set; }
#endregion
}
Model code:
public class FormModel : INotifyPropertyChanged, IDataErrorInfo
{
#region notifypropertychanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
#region dataerrorinfo
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "Name":
if (string.IsNullOrEmpty(Name))
{
return "Name is required";
}
break;
case "Age":
if (Age < 18 || Age > 50)
{
return "Are you kidding?";
}
break;
}
return null;
}
}
#endregion
#region properties
private string name;
public string Name { get { return name; } set { name = value; OnPropertyChanged("Name"); } }
private int age;
public int Age { get { return age; } set { age = value; OnPropertyChanged("Age"); } }
#endregion
}
Solution (at the moment)
<Style x:Key="InputControlDefault" TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="black"
FontSize="12pt">
?
</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="test"/>
<Setter Property="BorderBrush" Value="Red" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Validation.HasError" Value="true"/>
<Condition Property="Text" Value=""/>
</MultiTrigger.Conditions>
<Setter Property="BorderBrush" Value="Orange" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Foreground="Black" FontSize="12pt">
*
</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ToolTip" Value="test"/>
</MultiTrigger>
</Style.Triggers>
</Style>
i answer a similar question a few days ago:
first if your rule say that first and lastname should not be empty -
its right that the user see the validation error.
what i have done is to use a ValidationTemplate for empty/initial values, so
that the user just see a "*" for requiered field.
here is the complete answer
How can I change a Button template dynamically?
I have a ComboBox where by changing his selected value I want to change a Button Template.
This is what I have been trying to do:
<Window.Resources>
<ControlTemplate x:Key="ButtonControlTemplate1" TargetType="{x:Type Button}">
<Grid>
<Rectangle Fill="#FF2D2D7A" Margin="7.5,9.5,8.5,11" Stroke="Black"
RadiusX="45" RadiusY="45" StrokeThickness="6"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ButtonControlTemplate2" TargetType="{x:Type Button}">
<Grid>
<ed:RegularPolygon Fill="#FFE7F9C9" Height="Auto" InnerRadius="0.47211"
Margin="20.5,16,15.5,8" PointCount="5" Stretch="Fill"
Stroke="Black" StrokeThickness="6" Width="Auto"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<ComboBox Name="GroupBoxHeaderComboBox" ItemsSource="{Binding Path=collection}"
DisplayMemberPath="Key" Height="52" Margin="211.5,60,230.5,0"
VerticalAlignment="Top" SelectedIndex="1"/>
<Button Content="Button" HorizontalAlignment="Left" Height="102" Margin="47.5,0,0,91"
VerticalAlignment="Bottom" Width="132"
Template="{DynamicResource ButtonControlTemplate2}"/>
<Button Content="Button" HorizontalAlignment="Right" Height="112.5" Margin="0,0,27.5,85"
VerticalAlignment="Bottom" Width="153"
Template="{DynamicResource ButtonControlTemplate1}"/>
<Button Content="Button" Height="102" Margin="239.5,0,252.5,13.5"
VerticalAlignment="Bottom"
Template="{Binding ElementName=GroupBoxHeaderComboBox, Path=SelectedItem.Value}"/>
</Grid>
And here are the associated Templates:
<Window.Resources>
<ControlTemplate x:Key="ButtonControlTemplate1" TargetType="{x:Type Button}">
<Grid>
<Rectangle Fill="#FF2D2D7A" Margin="7.5,9.5,8.5,11" Stroke="Black"
RadiusX="45" RadiusY="45" StrokeThickness="6"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ButtonControlTemplate2" TargetType="{x:Type Button}">
<Grid>
<ed:RegularPolygon Fill="#FFE7F9C9" Height="Auto" InnerRadius="0.47211"
Margin="20.5,16,15.5,8" PointCount="5" Stretch="Fill"
Stroke="Black" StrokeThickness="6" Width="Auto"/>
</Grid>
</ControlTemplate>
</Window.Resources>
And the code behind:
public partial class MainWindow : Window
{
public Dictionary<string, string> collection
{
get;
private set;
}
public MainWindow()
{
this.InitializeComponent();
DataContext = this;
collection = new Dictionary<string, string>()
{
{ "DynamicResource ButtonControlTemplate2", "{DynamicResource ButtonControlTemplate2}"},
{ "DynamicResource ButtonControlTemplate1", "{DynamicResource ButtonControlTemplate2}"},
};
// Insert code required on object creation below this point.
}
}
Is there another genric way to acomplish this?... I want that most of the code would be xaml.
EDIT:
Is there a point to do it using a style? Let's say I want more then one object to act, otherwise is there a point to change the style and to do it all from there?
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public Dictionary<string, ControlTemplate> collection
{
get
{
Dictionary<string, ControlTemplate> controlTemplates = new Dictionary<string, ControlTemplate>();
controlTemplates.Add("ButtonControlTemplate1", FindResource("ButtonControlTemplate1") as ControlTemplate);
controlTemplates.Add("ButtonControlTemplate2", FindResource("ButtonControlTemplate2") as ControlTemplate);
return controlTemplates;
}
}
}
Create a ControlTemplate in Windows resource,
<Window.Resources>
<ControlTemplate x:Key="GreenTemplate" TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="Green"/>
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
Now in run time you can change the template property of button.
private void Button_Clicked(object sender, RoutedEventArgs e)
{
Button btn = e.OriginalSource as Button;
if (btn != null)
{
btn.Template = FindResource("GreenTemplate") as ControlTemplate;
}
}
You can use a data trigger and do it all in xaml.
This uses a tree but the concept is the same
<Window x:Class="WpfBindingTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfBindingTest"
Title="Window3" Height="300" Width="300" Name="win3" >
<Window.Resources>
<XmlDataProvider x:Key="treeData" XPath="*">
<x:XData>
<Items Name="Items" xmlns="">
<Item1/>
<Item2>
<Item22/>
<Item12/>
<Item13>
<Item131/>
<Item131/>
</Item13>
</Item2>
</Items>
</x:XData>
</XmlDataProvider>
<HierarchicalDataTemplate ItemsSource="{Binding XPath=child::*}" x:Key="template">
<TextBlock Name="textBlock" Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<StackPanel>
<TreeView ItemTemplate="{StaticResource template}"
Name="treeView"
ItemsSource="{Binding Source={StaticResource treeData}}">
<TreeView.ItemContainerStyle>
<!--Using style setter to set the TreeViewItem.IsExpanded property to true, this will be applied
to all TreeViweItems when they are generated-->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<Button Width="120" Height="30">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Content" Value="Default" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=treeView, Path=SelectedItem.Name}" Value="Item12">
<Setter Property="Content" Value="Now changed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
from here.
(I just googled to get an example faster)