MultiBinding ConvertBack issue - wpf

In trying to solve an issue Im having in another project - Ive created the following example to replicate the issue.
The idea is that when the user enters new values, via the slider or textbox, those values should then be "ConvertedBack" via the convertor, and the source updated. I don't seem to be seeing this though, I believe due to the fact that InternalRep's properties are being written to, but not informing the bindexpression for the InternalRepProperty.
What is the best way to go about solving this problem?
One method I tried was to handle the sliders ValueChanged event, but this caused the convertor to ... ConvertBack then Convert then ConvertBack then Convert, not sure why.
When the user changes a value, I need the convertor to only ConvertBack to update the source, and nothing else, .. is this possible?
TextSplitter XAML
<ContentControl x:Class="WpfApplication23.TextSplitter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:WpfApplication23"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UniformGrid Columns="3" Rows="2">
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}},
Path=InternalRep.First, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}},
Path=InternalRep.Second, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}},
Path=InternalRep.Third, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<Slider Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}},
Path=InternalRep.First, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Maximum="255" />
<Slider Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}},
Path=InternalRep.Second, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Maximum="255" />
<Slider Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}},
Path=InternalRep.Third, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Maximum="255" ValueChanged="OnSliderChnaged" />
</UniformGrid>
</ContentControl>
TextSplitter C#
public class InternalRep
{
public int First { get; set; }
public int Second { get; set; }
public int Third { get; set; }
};
public class LettersToInternalRepMultiConvertor : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
InternalRep ir = new InternalRep()
{
First = (int)(char)values[0],
Second = (int)(char)values[1],
Third = (int)(char)values[2],
};
return ir;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
InternalRep ir = (InternalRep)value;
if (ir != null)
{
return new object[]
{
(char)ir.First,
(char)ir.Second,
(char)ir.Third
};
}
else
{
throw new Exception();
}
}
}
public partial class TextSplitter : ContentControl
{
public static readonly DependencyProperty FirstProperty = DependencyProperty.Register(
"First", typeof(char), typeof(TextSplitter));
public static readonly DependencyProperty SecondProperty = DependencyProperty.Register(
"Second", typeof(char), typeof(TextSplitter));
public static readonly DependencyProperty ThirdProperty = DependencyProperty.Register(
"Third", typeof(char), typeof(TextSplitter));
public static readonly DependencyProperty InternalRepProperty = DependencyProperty.Register(
"InternalRep", typeof(InternalRep), typeof(TextSplitter));
BindingExpressionBase beb = null;
public TextSplitter()
{
InitializeComponent();
MultiBinding mb = new MultiBinding();
mb.Mode = BindingMode.TwoWay;
mb.Bindings.Add(SetBinding("First"));
mb.Bindings.Add(SetBinding("Second"));
mb.Bindings.Add(SetBinding("Third"));
mb.Converter = new LettersToInternalRepMultiConvertor();
beb = SetBinding(InternalRepProperty, mb);
}
Binding SetBinding(string _property)
{
Binding b = new Binding(_property);
b.Mode = BindingMode.TwoWay;
b.Source = this;
return b;
}
public char First
{
get { return (char)GetValue(FirstProperty); }
set { SetValue(FirstProperty, value); }
}
public char Second
{
get { return (char)GetValue(SecondProperty); }
set { SetValue(SecondProperty, value); }
}
public char Third
{
get { return (char)GetValue(ThirdProperty); }
set { SetValue(ThirdProperty, value); }
}
}
MainWindow XAML
<Window x:Class="WpfApplication23.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication23"
Title="MainWindow" Height="640" Width="800" WindowStartupLocation="CenterScreen">
<StackPanel>
<local:TextSplitter First="{Binding A, Mode=TwoWay}"
Second="{Binding B, Mode=TwoWay}"
Third="{Binding C, Mode=TwoWay}"/>
</StackPanel>
</Window>
MainWindow Code
namespace WpfApplication23
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
char m_a = 'a';
public char A
{
get { return m_a; }
set { m_a = value; }
}
char m_B = 'b';
public char B
{
get { return m_B; }
set{ m_B = value; }
}
char m_c = 'c';
public char C
{
get { return m_c; }
set { m_c = value; }
}
}
}

You need to implement INotifyPropertyChanged in your ViewModel.
Here, the ViewModel is the Window , so you have to do:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
char m_a = 'a';
public char A
{
get { return m_a; }
set
{
if (value != m_a)
{
m_c = value;
RaisePropertyChanged("A");
}
}
}
char m_B = 'b';
public char B
{
get { return m_B; }
set
{
if (value != m_B)
{
m_c = value;
RaisePropertyChanged("B");
}
}
}
char m_c = 'c';
public char C
{
get { return m_c; }
set
{
if (value != m_c)
{
m_c = value;
RaisePropertyChanged("C");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string _Prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(_Prop));
}
}
DelegateCommand _RecomputeCommand;
public DelegateCommand RecomputeCommand
{
get { return _RecomputeCommand ?? (_RecomputeCommand = new DelegateCommand(Recompute)); }
}
public void Recompute()
{
//Do something with A, B and C.
}
}
EDIT: you should simply bind the 3 sliders to A, B, C (you'll need the above implementation) and then act upon a RecomputeCommand like so:
<StackPanel>
<Slider Value="{Binding A}" Maximum="255">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<i:InvokeCommandAction Command="{Binding RecomputeCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
<Slider Value="{Binding B}" Maximum="255">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<i:InvokeCommandAction Command="{Binding RecomputeCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
<Slider Value="{Binding C}" Maximum="255">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<i:InvokeCommandAction Command="{Binding RecomputeCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
</StackPanel>
Of course this can be in a ContentControl as needed.

Related

Value of binded property don´t change when enter text in textbox (INotifyPropertyChanged implemented, MVVM)

I am trying to realize Wordle(Game) in WPF as a project to practice. In the ViewModel I have a Property(e.g. FirstWord) for the first, second, third, fourth and fifth word, all of which have INotifyPropertyChanged implemented. I would like to avoid creating a property for every single textbox (would be 5x5). Therefore, my model consists of 1st letter, 2nd letter, 3rd letter, etc. In XAML the binding looks like this: {Binding FirstWord.FirstLetter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}.
When debugging, the program does not even jump into the setter, it was not detected that the value has changed. What can be the reason for this?
ViewModel
public class WordleViewModel : ObservableObject
{
private int counter;
public string solutionWord;
private static int[] _stateColor = new int[5];
public static int[] StateColor
{
get { return _stateColor;} private set { _stateColor = value; }
}
private SafeWordsModel _firstWord;
public SafeWordsModel FirstWord
{
get { return _firstWord; }
set
{
_firstWord = value;
OnPropertyChanged();
}
}
//....
public WordleViewModel()
{
solutionWord = "TESTE";
}
}
Model (Do I really need variable + Property and the INotifyChanged here?)
public class SafeWordsModel : ObservableObject
{
private string _firstLetter;
private string _secondLetter;
private string _thirdLetter;
private string _fourthLetter;
private string _fifthLetter;
public string FirstLetter
{
get { return _firstLetter; }
set { _firstLetter = value; OnPropertyChanged(); }
}
public string SecondLetter
{
get { return _secondLetter; }
set { _secondLetter = value; OnPropertyChanged(); }
}
public string ThirdLetter
{
get { return _thirdLetter; }
set { _thirdLetter = value; OnPropertyChanged(); }
}
public string FourthLetter
{
get { return _fourthLetter; }
set { _fourthLetter = value; OnPropertyChanged(); }
}
public string FifthLetter
{
get { return _fifthLetter; }
set { _fifthLetter = value; OnPropertyChanged(); }
}
public SafeWordsModel()
{
this.FirstLetter = _firstLetter;
this.SecondLetter = _secondLetter;
this.ThirdLetter = _thirdLetter;
this.FourthLetter = _fourthLetter;
this.FifthLetter = _fifthLetter;
}
}
View
public WordleView()
{
InitializeComponent();
ViewModel = new WordleViewModel();
}
public WordleViewModel ViewModel
{
get { return DataContext as WordleViewModel; }
set { DataContext = value; }
}
public void FocusNext(object sender, TextChangedEventArgs e)
{
if (((TextBox)sender).MaxLength == ((TextBox)sender).Text.Length)
{
// move focus
var ue = e.OriginalSource as FrameworkElement;
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
XAML for one Word
<StackPanel Orientation="Horizontal"
FocusManager.FocusedElement="{Binding ElementName=tbx11}">
<TextBox x:Name="tbx11"
Style="{StaticResource Input_tbx}"
TextChanged="FocusNext"
Background="{Binding ElementName=Line2_ckb, Path=IsChecked, Converter={StaticResource brush1}}"
Text="{Binding FirstWord.FirstLetter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="tbx12"
Style="{StaticResource Input_tbx}"
TextChanged="FocusNext"
Background="{Binding ElementName=Line2_ckb, Path=IsChecked, Converter={StaticResource brush2}}"
Text="{Binding FirstWord.SecondLetter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="tbx13"
Style="{StaticResource Input_tbx}"
TextChanged="FocusNext"
Text="{Binding FirstWord.ThirdLetter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="tbx14"
Style="{StaticResource Input_tbx}"
TextChanged="FocusNext"
Background="{Binding ElementName=Line2_ckb, Path=IsChecked, Converter={StaticResource brush4}}"
Text="{Binding FirstWord.FourthLetter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="tbx15"
TextChanged="FocusNext"
Style="{StaticResource Input_tbx}"
Background="{Binding ElementName=Line2_ckb, Path=IsChecked, Converter={StaticResource brush5}}"
Text="{Binding FirstWord.FifthLetter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
ObservableObject Class
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
So far it has always worked well with INotifyPropertyChanged. It should set the value that comes from the textbox accordingly in the property and I can also use this value. Feel free to make suggestions on other places in the code, I'm still very new to WPF.

How to use Binding with a Binding in it

Hi following suggestion
you have a Textblock which DataContext is:
this[0]
this[1]
this[...]
this[n]
this Textblock is a child of a DatagridCell
now i want to get set a Binding based on the Column position
so i wrote Binding RelativeSource={RelativeSource FindAncestor,AncestorType=DataGridCell},Path=Column.DisplayIndex } which works fine
to get a this[...] i need to bind like Binding Path=[0] which also works well
but if i but both together like this:
{ Binding Path=[ {Binding Path=Column.DisplayIndex, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell}} ] }
it doesn't bind here the Error
System.Windows.Data Error: 40 : BindingExpression path error: '[]' property not found on 'object' ''List`1' (HashCode=33153114)'. BindingExpression:Path=[{Binding Path=Column.DisplayIndex, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell} }]; DataItem='List`1' (HashCode=33153114); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
so does anyone know how to do this?
Edit:
here simple code:
XAML
<DataGrid AutoGenerateColumns="true" Height="200" HorizontalAlignment="Left" Margin="243,12,0,0" Name="grid" VerticalAlignment="Top" Width="200"
SelectionMode="Extended" SelectionUnit="Cell">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Green"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<StackPanel >
<TextBlock Text="{Binding Path=Column.DisplayIndex, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell} }" />
<TextBlock Text="{Binding Path=[0] }" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
CS
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var gridList = new List<List<MyCell>>();
for (int i = 0; i < 5; i++)
{
var cell1 = new MyCell { Text1 = "nr " + i, Text2 = "value1" };
var cell2 = new MyCell { Text1 = "nr " + i, Text2 = "value2" };
var sublist = new List<MyCell>();
sublist.Add(cell1);
sublist.Add(cell2);
gridList.Add(sublist);
}
grid.ItemsSource = gridList;
}
}
public class MyCell
{
string value1;
string value2;
public string Text1
{
get { return value1; }
set { value1 = value; }
}
public string Text2
{
get { return value2; }
set { value2 = value; }
}
}
Interesting constillation you got there.
It is very well possible to do what you actually asking for.
The Binding Path has a property called PathParameters and here is how you can use it:
new Binding {
Path = new PropertyPath("Values[(0)]", new DateTime(2011, 01, 01))
}
In this example instead of zero the date is gonna be injected.
You will find here about path syntax:
http://msdn.microsoft.com/en-us/library/ms742451.aspx
Edit 2:
Hack wpf to make it work.
Lets say this is your ViewModel.
public class VM
{
private Dictionary<int, string> dic;
public Dictionary<int, string> Dic
{
get
{
if (dic == null)
{
dic = new Dictionary<int, string>();
dic[123] = "Hello";
}
return dic;
}
}
public int Index
{
get
{
return 123;
}
}
}
This is XAML:
I am having DataContext inside Resources as you can see.
<Window x:Class="WpfApplication1.MainWindow"
xmlns:helper="clr-namespace:WpfApplication1.Helper">
<Window.Resources>
<local:VM x:Key="viewModel"/>
</Window.Resources>
<StackPanel>
<Button>
<Button.Content>
<helper:ParameterBinding Source="{StaticResource viewModel}" PropertyName="Dic" HasIndex="True">
<helper:ParameterBinding.ParameterObject>
<helper:ParameterBindingHelperObject BindableParameter="{Binding Source={StaticResource viewModel}, Path=Index}"/>
</helper:ParameterBinding.ParameterObject>
</helper:ParameterBinding>
</Button.Content>
</Button>
</StackPanel>
</Window>
And this is the key to everything:
public class ParameterBinding : Binding
{
private ParameterBindingHelperObject parameterObject;
public ParameterBindingHelperObject ParameterObject
{
get
{
return parameterObject;
}
set
{
this.parameterObject = value;
this.parameterObject.Binding = this;
}
}
public bool HasIndex
{
get;
set;
}
public string PropertyName
{
get;
set;
}
public void UpdateBindingPath()
{
string path = this.PropertyName + (HasIndex ? "[" : "") + this.ParameterObject.BindableParameter + (HasIndex ? "]" : "");
this.Path = new PropertyPath(path);
}
}
public class ParameterBindingHelperObject : DependencyObject
{
private ParameterBinding binding;
public ParameterBinding Binding
{
get
{
return binding;
}
set
{
this.binding = value;
this.binding.UpdateBindingPath();
}
}
public object BindableParameter
{
get { return (object)GetValue(BindableParameterProperty); }
set { SetValue(BindableParameterProperty, value); }
}
public static readonly DependencyProperty BindableParameterProperty =
DependencyProperty.Register("BindableParameter", typeof(object), typeof(ParameterBindingHelperObject), new UIPropertyMetadata(null, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
ParameterBindingHelperObject obj = (ParameterBindingHelperObject)dependencyObject;
if (obj.Binding != null)
{
obj.Binding.UpdateBindingPath();
}
}
}
I inherit from Binding and place a bindable property which will serve as parameter.
Technically you can change this and make most awesome binding ever. You could allow to change index number at runtime and path will change.
What you think of this?

WPF Datagrid hierarchical data recorded as strings

I have information that was gathered from a service about TFS builds put into ViewModels.
Here are the models:
public class Collection : ViewModel
{
private string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged(() => Name);
}
}
private ObservableCollection<Project> _projects = new ObservableCollection<TFSProject>();
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
_projects = value;
OnPropertyChanged(() => Projects);
}
}
}
public class Project : ViewModel
{
private string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged(() => Name);
}
}
private ObservableCollection<string> _buildDefinitions = new ObservableCollection<string>();
public ObservableCollection<string> BuildDefinitions
{
get { return _buildDefinitions; }
set
{
_buildDefinitions = value;
OnPropertyChanged(() => BuildDefinitions);
}
}
}
I am binding my combobox's itemssource to a ObservableCollection<Collection>. The problem is that the collection, project, and build definition names are stored in a class that defines them as separate string properties:
public class BuildMonitor : INotifyPropertyChanged
{
[Description("In TFS, each team project exists within a TFS Collection. This is the name of the collection applicable to the build that this monitor is for. Default='Vision2010'")]
public string Collection
{
get { return collection_; }
set
{
collection_ = value;
OnPropertyChanged(() => Collection);
}
}
private string collection_ = "Vision2010";
[Description("BuildDefintions reside within a TeamProject. This is the name of the TeamProject where the build definition this monitor is for resides. Default='Double-Take2010'")]
public string TeamProject { get { return teamProject_; } set { teamProject_ = value; OnPropertyChanged(() => TeamProject); } }
private string teamProject_ = "Double-Take2010";
[Description("Builds are defined in TFS as the execution of a particular BuildDefinition. This is the name of the build defintion (thus; the build) this monitor is for.")]
public string BuildDefinition { get { return buildDefinition_; } set { buildDefinition_ = value; OnPropertyChanged(() => BuildDefinition); } }
private string buildDefinition_;
[Description("Used only if this monitor should watch for builds specified by a particular user. Enter the domain name of the user, or leave blank to monitor builds by any user.")]
public string RequestedByFilter { get { return requestedByFilter_; } set { requestedByFilter_ = value; OnPropertyChanged(() => RequestedByFilter); } }
private string requestedByFilter_;
[Description("The command to execute when the build monitor is triggered.")]
public string Command { get { return command_; } set { command_ = value; OnPropertyChanged(() => Command); } }
private string command_;
[Description("The arguments to pass to the command. Arguments will resolve known build monitor macros.")]
public string Arguments { get { return arguments_; } set { arguments_ = value; OnPropertyChanged(() => Arguments); } }
private string arguments_;
[Description("If TRUE, the monitor will fire only once, at which point it will be marked as 'invalid' and never fire again.")]
public bool RunOnce { get { return runOnce_; } set { runOnce_ = value; OnPropertyChanged(() => RunOnce); } }
private bool runOnce_ = false;
[Description("The maximum age (in hours) a build can be (since finished), for the monitor to consider it for processing. Default='0'")]
public int MaxAgeInHours { get { return maxAgeInHours_; } set { maxAgeInHours_ = value; OnPropertyChanged(() => MaxAgeInHours); } }
private int maxAgeInHours_ = 0;
[Description("Which status trigger the monitor should 'fire' on. When the build status matches this trigger, the monitor command will be executed. Default='Succeeded'")]
public BuildStatus EventTrigger { get { return eventTrigger_; } set { eventTrigger_ = value; OnPropertyChanged(() => EventTrigger); } }
private BuildStatus eventTrigger_ = BuildStatus.Succeeded;
[Browsable(false), Description("Used internally to reliably compare two BuildMonitors against each other.")]
public Guid ID { get { return id_; } set { id_ = value; } }
private Guid id_ = Guid.NewGuid();
[Browsable(false), Description("Used internally to determine if the monitor is still valid/should be processed.")]
public bool IsEnabled { get { return isEnabled_; } set { isEnabled_ = value; } }
private bool isEnabled_ = true;
[Browsable(false), XmlIgnore, Description("Used internally to track when the monitor is 'busy' (currently running the 'Command' selected.")]
public int CurrentProcessID { get { return currentProcessID_; } set { currentProcessID_ = value; } }
private int currentProcessID_ = 0;
[Browsable(false), XmlIgnore, Description("Used internally to track the build that the monitor is currently processing.")]
private string currentBuildUri_;
public string CurrentBuildUri { get { return currentBuildUri_; } set { currentBuildUri_ = value; } }
[field: NonSerialized, Browsable(false)]
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
MemberExpression memberExpression = (MemberExpression)propertyExpression.Body;
string propertyName = memberExpression.Member.Name;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In my xaml I have attempted to represent the selection of this data by setting the itemssource of the collection combobbox to a relative source binding which is the ObservableCollection<Collection>. I get the items in the list ok but since the itemssource is a List<BuildMonitors>, I can't seem to get the selected item to map over the name property of the selected item to the actual binding of the data item (string Collection in the BuildMonitor instance).
<tk:DataGrid ItemsSource="{Binding Monitors}"
AutoGenerateColumns="False">
<tk:DataGrid.Columns>
<tk:DataGridTemplateColumn Header="Collection">
<tk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="Collection"
ItemsSource="{Binding Path=AllCollections, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type apollo:BuildMonitorNew}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding .}"
SelectedValuePath="Collection"
SelectedValue="{Binding Name}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellEditingTemplate>
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Collection,Mode=TwoWay}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn Header="Project">
<tk:DataGridTemplateColumn.CellEditingTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ElementName=Collection,Path=SelectedItem.Projects}">
<ComboBox x:Name="Projects"
ItemsSource="{Binding}"
DisplayMemberPath="Name"/>
</HierarchicalDataTemplate>
</tk:DataGridTemplateColumn.CellEditingTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTextColumn Binding="{Binding Command}"
Header="Command"/>
<tk:DataGridTextColumn Binding="{Binding Arguments}"
Header="Arguments"
Width="*"/>
</tk:DataGrid.Columns>
My first thought is that although my viewmodel may be a better representation of the data (hierarchical), the structure of the data to select vs the data to actual store is too different.
I would love to be wrong here and find a snazzy way to convert the data that is actually selected(Collection,Project, and then BuildDefinition) to the path of the data that is stored (BuildMonitor).
Any ideas?
I found that the multivalue converter allowed me to transform the hierarchical structure into parts for the combobox item source where they are representing a list.
Converter:
public class BuildMonitorItemSource : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.Count() == 2)
{
string collectionToGet = parameter.ToString();
ObservableCollection<TFSCollection> allcollections = values[1] as ObservableCollection<TFSCollection>;
BuildLauncher.BuildMonitor currentBM = (values[0] as BuildLauncher.BuildMonitor);
if (collectionToGet.Equals("Projects"))
{
return allcollections.FirstOrDefault(x => x.Name.Equals(currentBM.Collection, StringComparison.OrdinalIgnoreCase)).Projects;
}
else if (collectionToGet.Equals("BuildDefinitions"))
{
TFSCollection currentCollection = allcollections.FirstOrDefault(x => x.Name.Equals(currentBM.Collection));
TFSProject currentProject = currentCollection.Projects.FirstOrDefault(x => x.Name.Equals(currentBM.TeamProject));
return currentProject.BuildDefinitions;
}
}
return values;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This is the modified xml:
<tk:DataGrid ItemsSource="{Binding Monitors}"
AutoGenerateColumns="False"
IsEnabled="{Binding RelativeSource={RelativeSource AncestorType=apollo:BuildMonitorNew}, Path=TFSAuthenticated}"
Name="MontiorsGrid">
<tk:DataGrid.Columns>
<tk:DataGridCheckBoxColumn Binding="{Binding IsEnabled}">
<tk:DataGridCheckBoxColumn.Header>
<Ellipse Grid.Column="0"
HorizontalAlignment="Left"
Height="10" Width="10"
Stroke="Black"
StrokeThickness="1"
Fill="Green"/>
</tk:DataGridCheckBoxColumn.Header>
</tk:DataGridCheckBoxColumn>
<tk:DataGridTemplateColumn Header="Collection">
<tk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="CollectionCombo"
ItemsSource="{Binding Path=AllCollections, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type apollo:BuildMonitorNew}}}"
DisplayMemberPath="Name"
SelectedValue="{Binding Collection}"
SelectedValuePath="Name">
</ComboBox>
</DataTemplate>
</tk:DataGridTemplateColumn.CellEditingTemplate>
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Collection,Mode=TwoWay}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn Header="Project">
<tk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="Projects"
DisplayMemberPath="Name"
SelectedValue="{Binding TeamProject}"
SelectedValuePath="Name">
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource GetItemSource}" ConverterParameter="Projects" >
<Binding Path="." />
<Binding Path="AllCollections" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType={x:Type apollo:BuildMonitorNew}}"/>
</MultiBinding>
</ComboBox.ItemsSource>
</ComboBox>
</DataTemplate>
</tk:DataGridTemplateColumn.CellEditingTemplate>
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding TeamProject,Mode=TwoWay}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn Header="Build Definition">
<tk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding BuildDefinition}">
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource GetItemSource}" ConverterParameter="BuildDefinitions">
<Binding Path="." />
<Binding Path="AllCollections" RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType={x:Type apollo:BuildMonitorNew}}"/>
</MultiBinding>
</ComboBox.ItemsSource>
</ComboBox>
</DataTemplate>
</tk:DataGridTemplateColumn.CellEditingTemplate>
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding BuildDefinition,Mode=TwoWay}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTextColumn Binding="{Binding Command}"
Header="Command"/>
<tk:DataGridTextColumn Binding="{Binding Arguments}"
Header="Arguments"
Width="*"/>
</tk:DataGrid.Columns>
</tk:DataGrid>

Add a checkbox in WPF datagrid header and use it to select/unselect all checkboxes in DataGridCheckBoxColumn

I am new to WPF and would appreciate help on following problem.
In my wpf datagrid I have DataGridCheckBoxColumn as first column and I have bind this column to IsSelected property in ViewModel.
<toolkit:DataGridCheckBoxColumn Header="Title" Binding="{Binding isSelected}"/>
I also want to have a checkbox in header row and which I intend to use to select/unselect all the checkboxes in this column.
Till now I have managed to get a checkbox in header by applying a headerstyle as shown in below code snip but I am not able to toggle selection for all the checkboxes in column
<Style x:Key="CheckBoxHeaderStyle" TargetType="{x:Type toolkit:DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type toolkit:DataGridColumnHeader}">
<CheckBox x:Name="chkToggleSelection" VerticalAlignment="Center">
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You will have to handle the Checkbox.Click event (if you are using MVVM then set the CheckBox.Command or use attached behavior to handle the event) and then set the boolean property of all items bound to the datagrid as true \ false accordingly.
Sadly there is no other alternative that I am aware of!
<DataGrid x:Name="DgLines" ItemsSource="{Binding OpcUaEndpoints}" AutoGenerateColumns="False"
SelectionMode="Extended" IsReadOnly="True" Grid.ColumnSpan="5">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Name="ckbSelectedAll"
IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked" >
<i:InvokeCommandAction Command="{Binding DataContext.CheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked" >
<i:InvokeCommandAction Command="{Binding DataContext.UncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="cbkSelect"
IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--<DataGridTextColumn Width="200" Header="Id" Binding="{Binding Id }" />-->
<DataGridTextColumn Width="200" Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Width="500" Header="Description" Binding="{Binding Description}"/>
<DataGridTextColumn Width="500" Header="Lines" Binding="{Binding Endpoint}"/>
</DataGrid.Columns>
</DataGrid>
public class OpcUaEndpointsListViewModel : INotifyPropertyChanged
{
private static OpcUaEndpointsListViewModel _instance;
private static readonly object Padlock = new object();
private ICommand _addCommand;
//private ICommand _uncheckCommand;
private ICommand _searchcommand;
private ICommand _checkedCommand { get; set; }
private ICommand _unCheckedCommand { get; set; }
private ObservableCollection<AddOpcUaEndpointsViewModel> _endpoint;
public string _charNameFromTB;
public event PropertyChangedEventHandler PropertyChanged;
public OpcUaEndpointsListViewModel()
{
BindDataGrid();
}
public static OpcUaEndpointsListViewModel Instance
{
get
{
lock (Padlock)
{
return _instance ?? (_instance = new OpcUaEndpointsListViewModel());
}
}
}
/// <summary>
/// //OPC UA Endpoint List
/// </summary>
public ObservableCollection<AddOpcUaEndpointsViewModel> OpcUaEndpoints
{
get { return _endpoint; }
set
{
if (OpcUaEndpoints == value)
{
_endpoint = value;
OnPropertyChanged("OpcUaEndpoints");
}
}
}
public string CharNameFromTB
{
get { return _charNameFromTB; }
set
{
_charNameFromTB = value;
OnPropertyChanged("CharNameFromTB");
}
}
public ICommand AddCommand
{
get { return _addCommand ?? (_addCommand = new WpfApplication1.RelayCommand<object>(p => ExecuteAddCommand())); }
}
public ICommand SearchCommand
{
get { return _searchcommand ?? (_searchcommand = new RelayCommand<object>(p => ExecuteSearchCommand())); }
}
public ICommand CheckedCommand
{
get { return _checkedCommand ?? (_checkedCommand = new WpfApplication1.RelayCommand<object>(p => ExecuteCheckedCommand())); }
}
public ICommand UncheckedCommand
{
get { return _unCheckedCommand ?? (_unCheckedCommand = new WpfApplication1.RelayCommand<object>(p => ExecuteUnCheckedCommand())); }
}
private void ExecuteSearchCommand()
{
///BindDataGridsearch();
}
private void ExecuteCheckedCommand()
{
foreach (var item in _endpoint)
{
item.IsSelected = true;
}
}
private void ExecuteUnCheckedCommand()
{
foreach (var item in _endpoint)
{
item.IsSelected = false;
}
}
private void ExecuteAddCommand()
{
///BindDataGridsearch();
}
private void BindDataGrid()
{
_endpoint = new ObservableCollection<AddOpcUaEndpointsViewModel>();
_endpoint.Add(new AddOpcUaEndpointsViewModel { Name = "A", Description = "A", Endpoint = 1 });
_endpoint.Add(new AddOpcUaEndpointsViewModel { Name = "B", Description = "B", Endpoint = 2 });
_endpoint.Add(new AddOpcUaEndpointsViewModel { Name = "C", Description = "C", Endpoint = 3 });
_endpoint.Add(new AddOpcUaEndpointsViewModel { Name = "D", Description = "D", Endpoint = 4 });
_endpoint.Add(new AddOpcUaEndpointsViewModel { Name = "E", Description = "E", Endpoint = 5 });
}
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class AddOpcUaEndpointsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value; OnPropertyChanged("Description"); }
}
private int _endPoint;
public int Endpoint
{
get { return _endPoint; }
set { _endPoint = value; OnPropertyChanged("Endpoint"); }
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; OnPropertyChanged("IsSelected"); }
}
}

WPF List View Sort on Load

Here is the problem:
I want to sort a ListView when it is first loaded.
I have implemented the functionality where in the List View can be sorted if the Header columns
in the ListView are clicked.
I unable to find a suitable event which I can use to call my sort function.
I tried using OnInitialized of the UserControl and Loaded events but it seems the List View is
not populated when I call these functions.
I tried GotFocus of ListView. It works but then I have to click on the window to get the sorting done.
I want the sorting to be done as soon as the ListView is loaded.
I am using XML data binding with the List View.
The ListView is part of a UserControl. The User Control is hosted in a MMC app.
Please let me know if you need any other information.
public class SortableGridViewColumn : GridViewColumn
{
public string SortPropertyName
{
get { return (string)GetValue(SortPropertyNameProperty); }
set { SetValue(SortPropertyNameProperty, value); }
}
// Using a DependencyProperty as the backing store for SortPropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortPropertyNameProperty =
DependencyProperty.Register("SortPropertyName", typeof(string),
typeof(SortableGridViewColumn), new UIPropertyMetadata(""));
public bool IsDefaultSortColumn
{
get { return (bool)GetValue(IsDefaultSortColumnProperty); }
set { SetValue(IsDefaultSortColumnProperty, value); }
}
public static readonly DependencyProperty IsDefaultSortColumnProperty =
DependencyProperty.Register("IsDefaultSortColumn", typeof(bool),
typeof(SortableGridViewColumn), new UIPropertyMetadata(false));
}
public class SortableListView : ListView
{
public SortableListView()
{
}
SortableGridViewColumn lastSortedOnColumn = null;
ListSortDirection lastDirection = ListSortDirection.Ascending;
public void Sort(string sortBy, ListSortDirection direction)
{
ICollectionView dataView = CollectionViewSource.GetDefaultView
(this.ItemsSource);
//Check if dataView isn't null
if (dataView != null)
{
dataView.SortDescriptions.Clear();
SortDescription sd1 = new SortDescription("#isenabled", direction);
dataView.SortDescriptions.Add(sd1);
SortDescription sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
}
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked != null &&
headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
// attempt to cast to the sortableGridViewColumn object.
SortableGridViewColumn sortableGridViewColumn = (headerClicked.Column) as SortableGridViewColumn;
// ensure that the column header is the correct type and a sort property has been set.
if (sortableGridViewColumn != null && !String.IsNullOrEmpty(sortableGridViewColumn.SortPropertyName))
{
ListSortDirection direction;
bool newSortColumn = false;
// determine if this is a new sort, or a switch in sort direction.
if (lastSortedOnColumn == null
|| String.IsNullOrEmpty(lastSortedOnColumn.SortPropertyName)
|| !String.Equals(sortableGridViewColumn.SortPropertyName, lastSortedOnColumn.SortPropertyName, StringComparison.InvariantCultureIgnoreCase))
{
newSortColumn = true;
direction = ListSortDirection.Ascending;
}
else
{
if (lastDirection == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
}
// get the sort property name from the column's information.
string sortPropertyName = sortableGridViewColumn.SortPropertyName;
// Sort the data.
Sort(sortPropertyName, direction);
lastSortedOnColumn = sortableGridViewColumn;
lastDirection = direction;
}
}
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.
this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));
// cast the ListView's View to a GridView
GridView gridView = this.View as GridView;
if (gridView != null)
{
// determine which column is marked as IsDefaultSortColumn. Stops on the first column marked this way.1
SortableGridViewColumn sortableGridViewColumn = null;
foreach (GridViewColumn gridViewColumn in gridView.Columns)
{
sortableGridViewColumn = gridViewColumn as SortableGridViewColumn;
if (sortableGridViewColumn != null)
{
if (sortableGridViewColumn.IsDefaultSortColumn)
{
break;
}
sortableGridViewColumn = null;
}
}
// if the default sort column is defined, sort the data
if (sortableGridViewColumn != null)
{
lastSortedOnColumn = sortableGridViewColumn;
Sort(sortableGridViewColumn.SortPropertyName, ListSortDirection.Ascending);
}
}
}
}
The XAML is as shown below:
**<local:SortableListView x:Name="ListViewControl" Grid.Row="0" ItemContainerStyle="{DynamicResource StretchedContainerStyle}"
ItemTemplateSelector="{DynamicResource myControlTemplateSelector}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource dataProvider},
XPath=//CONFIGURATION}">
<ListView.View >
<GridView >
<local:SortableGridViewColumn Header="ID" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#id}"
IsDefaultSortColumn="True"
SortPropertyName="#id"/>
<local:SortableGridViewColumn Header="VALUE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
CellTemplateSelector="{DynamicResource myControlTemplateSelector}"
SortPropertyName="#value"/>
<local:SortableGridViewColumn Header="DATATYPE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#data_type}"
SortPropertyName="#data_type"/>
<local:SortableGridViewColumn Header="DESCRIPTION" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#description}"
SortPropertyName="#description"
Width="{Binding ElementName=ListViewControl, Path=ActualWidth}"/>
</GridView>
</ListView.View>
</local:SortableListView>**
<StackPanel Grid.Row="1">
<Button Grid.Row="1" HorizontalAlignment="Stretch" Height="34" HorizontalContentAlignment="Stretch" >
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Orientation="Horizontal" FlowDirection="RightToLeft" Height="30">
<Button Grid.Row="1" Content ="Apply" Padding="0,0,0,0 " Margin="6,2,0,2" Name="btn_Apply" HorizontalAlignment="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="132" IsTabStop="True" Click="btn_ApplyClick" Height="24" />
</StackPanel >
</Button>
</StackPanel >
</Grid>
I finally was able to resolve it.
I had to use the Converter on ListView ItemSource. And then sort the List on Convert Function.
Here is the code below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.Threading;
using System.Collections.ObjectModel;
namespace ...MiscellaneousCAESettings
{
/// <summary>
/// Interaction logic for ConfigDataView.xaml
/// </summary>
public partial class ConfigDataView : UserControl, IConfigDataViewControl
{
ConfigDataViewPresenter _presenter = null;
public static string _currDataType = "";
public static string _min = "" ;
public static string _max = "";
public string Min
{
get
{
return _min;
}
set
{
_min = value ;
}
}
public string Max
{
get
{
return _max;
}
set
{
_max = value;
}
}
public string CurrDataType
{
get
{
return _currDataType;
}
set
{
_currDataType = value;
}
}
public ConfigDataView()
{
InitializeComponent();
//To give the classic windows look
Uri uri = new Uri("PresentationFramework.Classic;V3.0.0.0;31bf3856ad364e35;component\\themes/classic.xaml", UriKind.Relative);
this.Resources.MergedDictionaries.Add(Application.LoadComponent(uri) as ResourceDictionary);
}
private void txtBoxGotFocus(object sender, RoutedEventArgs e)
{
Min = "" ;
Max = "" ;
TextBox txtbox = e.Source as TextBox;
this.ListViewControl.SelectedItem = txtbox.DataContext;
//index
int index = this.ListViewControl.Items.IndexOf(this.ListViewControl.SelectedItem);
System.ComponentModel.ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ListViewControl.ItemsSource);
object stCurr = (dataView.CurrentPosition ) ;
//Check if the "data_type" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["data_type"] != null)
{
CurrDataType = ((XmlElement)dataView.CurrentItem).Attributes["data_type"].Value;
}
//Check if the "min" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["min"] != null)
{
Min = ((XmlElement)dataView.CurrentItem).Attributes["min"].Value;
}
//Check if the "min" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["max"] != null)
{
Max = ((XmlElement)dataView.CurrentItem).Attributes["max"].Value;
}
}
#region IConfigDataViewControl Members
public void LoadRootConfigData(string xmlFileName, string xmlFileContent, string xmlXPath)
{
try
{
XmlDocument configFileDoc = new XmlDocument();
configFileDoc.LoadXml(xmlFileContent);
XmlDataProvider xmldp = (XmlDataProvider)this.TryFindResource("dataProvider");
xmldp.Document = configFileDoc;
if (string.IsNullOrEmpty(xmlXPath))
{
xmldp.XPath = #"//node()[1]/node()[#value]";
}
else
{
xmldp.XPath = xmlXPath;
}
Binding bnd = new Binding();
bnd.Source = xmldp;
bnd.Converter = new SortList();
ListViewControl.SetBinding(ItemsControl.ItemsSourceProperty, bnd);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void LoadCategoryConfigData(string xmlFile, string xmlFileContent, string CategoryNodeName)
{
try
{
XmlDocument configFileDoc = new XmlDocument();
configFileDoc.LoadXml(xmlFileContent);
XmlDataProvider xmldp = (XmlDataProvider)this.TryFindResource("dataProvider");
xmldp.Document = configFileDoc;
xmldp.XPath = #"//CONTEXT[#id='" + CategoryNodeName + #"']/CONFIGURATION";
Binding bnd = new Binding();
bnd.Source = xmldp;
bnd.Converter = new SortList();
ListViewControl.SetBinding(ItemsControl.ItemsSourceProperty, bnd);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void AttachPresenter(ConfigDataViewPresenter cfgpresenter)
{
_presenter = cfgpresenter;
}
#endregion
private void btn_ApplyClick(object sender, RoutedEventArgs e)
{
XmlDataProvider odp = (XmlDataProvider)this.TryFindResource("dataProvider");
XmlDocument configFileDoc = new XmlDocument();
configFileDoc =odp.Document;
_presenter.Save(configFileDoc.InnerXml );
}
}
public class TextBoxMinMaxValidation : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
try
{
//Check for min max string length if it is a "Text" data type
if (ConfigDataView._currDataType.ToLower() == "text")
{
int minLength = Convert.ToInt32(ConfigDataView._min);
int maxLength = Convert.ToInt32(ConfigDataView._max);
int strLength = value.ToString().Length;
bool isValidLength = true;
isValidLength = ((strLength >= minLength) && (strLength <= maxLength));
if (!isValidLength)
{
return new ValidationResult(false, string.Format("The input String Length is out of range. The String Length should be between {0} to {1}", minLength, maxLength));
}
else
{
return new ValidationResult(true, null);
}
}
//Check for min max string length if it is a "Numeric" data type
if (ConfigDataView._currDataType.ToLower() != "numeric")
{
return new ValidationResult(true, null);
}
int min = Convert.ToInt32(ConfigDataView._min);
int max = Convert.ToInt32(ConfigDataView._max);
int res ;
bool isNumber = int.TryParse(value.ToString(), out res);
bool isValidRange = true;
if (!isNumber)
{
return new ValidationResult(false, "The input string is in incorrect format. Should be a Number.");
}
isValidRange = ((res >= min) && (res <= max));
if (!isValidRange)
{
return new ValidationResult(false, string.Format("The input integer is out of range. The number should be between {0} to {1}", min, max));
}
}
catch
{
}
return new ValidationResult(true, null);
}
}
public class ControlTemplateSelector : DataTemplateSelector
{
public const String XML_TAG_DATATYPE = "data_type";
public const String DATATYPE_DROPDOWN = "Dropdown";
public const String DATATYPE_BOOLEAN = "Boolean";
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
FrameworkElement window = (container as FrameworkElement);
try
{
XmlNode node = (XmlNode)item;
String dataType = "";
if (node.Attributes[XML_TAG_DATATYPE] != null)
{
dataType = (string)node.Attributes.GetNamedItem(XML_TAG_DATATYPE).Value;
}
if (dataType == DATATYPE_DROPDOWN)
{
return window.FindResource("dropDownTemplate") as DataTemplate;
}
if (dataType == DATATYPE_BOOLEAN)
{
return window.FindResource("booldropDownTemplate") as DataTemplate;
}
}
catch (Exception ex)
{
MessageBox.Show("Select template Exception" + ex.Message );
}
return window.FindResource("textTemplate") as DataTemplate;
}
}
public class boolConverter : IValueConverter
{
public const String XML_TAG_VALUE = "value";
public const String XML_TAG_ID = "id";
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Boolean boolVal = false;
try
{
boolVal = System.Convert.ToBoolean(value);
}
catch
{
string strVal = value.ToString();
int iVal = int.Parse(strVal);
boolVal = System.Convert.ToBoolean(iVal);
}
if (boolVal == true)
{
return 1;
}
else
{
return 0;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Boolean boolVal = false;
try
{
boolVal = System.Convert.ToBoolean(value);
}
catch
{
string strVal = value.ToString();
int iVal = int.Parse(strVal);
boolVal = System.Convert.ToBoolean(iVal);
}
return boolVal;
}
}
public class SortableGridViewColumn : GridViewColumn
{
public string SortPropertyName
{
get { return (string)GetValue(SortPropertyNameProperty); }
set { SetValue(SortPropertyNameProperty, value); }
}
// Using a DependencyProperty as the backing store for SortPropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortPropertyNameProperty =
DependencyProperty.Register("SortPropertyName", typeof(string),
typeof(SortableGridViewColumn), new UIPropertyMetadata(""));
public bool IsDefaultSortColumn
{
get { return (bool)GetValue(IsDefaultSortColumnProperty); }
set { SetValue(IsDefaultSortColumnProperty, value); }
}
public static readonly DependencyProperty IsDefaultSortColumnProperty =
DependencyProperty.Register("IsDefaultSortColumn", typeof(bool),
typeof(SortableGridViewColumn), new UIPropertyMetadata(false));
}
public class SortableListView : ListView
{
public SortableListView()
{
// add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.
this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));
}
SortableGridViewColumn lastSortedOnColumn = null;
ListSortDirection lastDirection = ListSortDirection.Ascending;
public void Sort(string sortBy, ListSortDirection direction)
{
ICollectionView dataView = CollectionViewSource.GetDefaultView
(this.ItemsSource);
//Check if dataView isn't null
if (dataView != null)
{
dataView.SortDescriptions.Clear();
SortDescription sd1 = new SortDescription("#isenabled", direction);
dataView.SortDescriptions.Add(sd1);
SortDescription sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
}
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked != null &&
headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
// attempt to cast to the sortableGridViewColumn object.
SortableGridViewColumn sortableGridViewColumn = (headerClicked.Column) as SortableGridViewColumn;
// ensure that the column header is the correct type and a sort property has been set.
if (sortableGridViewColumn != null && !String.IsNullOrEmpty(sortableGridViewColumn.SortPropertyName))
{
ListSortDirection direction;
// determine if this is a new sort, or a switch in sort direction.
if (lastSortedOnColumn == null
|| String.IsNullOrEmpty(lastSortedOnColumn.SortPropertyName)
|| !String.Equals(sortableGridViewColumn.SortPropertyName, lastSortedOnColumn.SortPropertyName, StringComparison.InvariantCultureIgnoreCase))
{
direction = ListSortDirection.Descending;
}
else
{
if (lastDirection == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
}
// get the sort property name from the column's information.
string sortPropertyName = sortableGridViewColumn.SortPropertyName;
// Sort the data.
Sort(sortPropertyName, direction);
lastSortedOnColumn = sortableGridViewColumn;
lastDirection = direction;
}
}
}
}
public class SortList : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//If the value is null tell binding engine to do nothing
if (value == null)
{
return Binding.DoNothing;
}
ListCollectionView view = (ListCollectionView)
CollectionViewSource.GetDefaultView(value);
SortDescription sort_isdisabled =
new SortDescription("#isenabled",
ListSortDirection.Ascending);
view.SortDescriptions.Add(sort_isdisabled);
SortDescription sort_id =
new SortDescription("#id",
ListSortDirection.Ascending);
view.SortDescriptions.Add(sort_id);
return view;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
}
The XAML is as follows:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:.....MiscellaneousCAESettings"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" >
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="textTemplate">
<TextBox HorizontalAlignment= "Stretch"
IsEnabled="{Binding XPath=./#isenabled}"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
GotFocus="txtBoxGotFocus"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding XPath="./#value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:TextBoxMinMaxValidation>
<local:TextBoxMinMaxValidation.DataType>
<local:DataTypeCheck
Datatype="{Binding Source={StaticResource dataProvider}, XPath='/[#id=CustomerServiceQueueName]'}"/>
</local:TextBoxMinMaxValidation.DataType>
<local:TextBoxMinMaxValidation.ValidRange>
<local:Int32RangeChecker
Minimum="{Binding Source={StaticResource dataProvider}, XPath=./#min}"
Maximum="{Binding Source={StaticResource dataProvider}, XPath=./#max}"/>
</local:TextBoxMinMaxValidation.ValidRange>
</local:TextBoxMinMaxValidation>
</Binding.ValidationRules>
</Binding >
</TextBox.Text>
</TextBox>
</DataTemplate>
<DataTemplate x:Key="dropDownTemplate">
<ComboBox Name="cmbBox" HorizontalAlignment="Stretch"
SelectedIndex="{Binding XPath=./#value}"
ItemsSource="{Binding XPath=.//OPTION/#value}"
IsEnabled="{Binding XPath=./#isenabled}"
/>
</DataTemplate>
<DataTemplate x:Key="booldropDownTemplate">
<ComboBox Name="cmbBox" HorizontalAlignment="Stretch"
SelectedIndex="{Binding XPath=./#value, Converter={StaticResource boolconvert}}">
<ComboBoxItem>True</ComboBoxItem>
<ComboBoxItem>False</ComboBoxItem>
</ComboBox>
</DataTemplate>
<local:ControlTemplateSelector
x:Key="myControlTemplateSelector"/>
<Style x:Key="StretchedContainerStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template" Value="{DynamicResource ListBoxItemControlTemplate1}"/>
</Style>
<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
<Border SnapsToDevicePixels="true" x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" Padding="{TemplateBinding Padding}" BorderThickness="0,0.5,0,0.5">
<GridViewRowPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
<Style x:Key="CustomHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Padding" Value="2,0,2,0"/>
</Style>
</UserControl.Resources>
<Grid x:Name="GridViewControl" Height="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="34"/>
</Grid.RowDefinitions>
<ListView x:Name="ListViewControl" Grid.Row="0" ItemContainerStyle="{DynamicResource StretchedContainerStyle}"
ItemTemplateSelector="{DynamicResource myControlTemplateSelector}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource dataProvider},
XPath=//CONFIGURATION}">
<ListView.View >
<GridView >
<GridViewColumn Header="ID" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" DisplayMemberBinding="{Binding XPath=./#id}"/>
<GridViewColumn Header="VALUE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" CellTemplateSelector="{DynamicResource myControlTemplateSelector}" />
<GridViewColumn Header="DATATYPE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" DisplayMemberBinding="{Binding XPath=./#data_type}"/>
<GridViewColumn Header="DESCRIPTION" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#description}"
Width="{Binding ElementName=ListViewControl, Path=ActualWidth}"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="1">
<Button Grid.Row="1" HorizontalAlignment="Stretch" Height="34" HorizontalContentAlignment="Stretch" >
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Orientation="Horizontal" FlowDirection="RightToLeft" Height="30">
<Button Grid.Row="1" Content ="Apply" Padding="0,0,0,0 " Margin="6,2,0,2" Name="btn_Apply" HorizontalAlignment="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="132" IsTabStop="True" Click="btn_ApplyClick" Height="24" />
</StackPanel >
</Button>
</StackPanel >
</Grid>

Resources