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?
Related
I need help implementing INotifyDataErrorInfo interface with SfTextBoxEx placed inside SfTextInputLayout.
WPF with MVVM using Caliburn.Micro framework.
Please find following project structure to understand by question.
1)
FolderName : Infrastructure
File : ValidatedPropertyChangedBase.cs
--It contains implementation for INotifyDataErrorInfo.
2)
FolderName : Model
File : TestModel.cs
--Contains two properties with inherited from ValidatedPropertyChangedBase
--Also contains the class for Validation of properties using FluentValidation
4)
FolderName : View
File : HomeView.xaml
--Standard window with two SfTextInputLayout with SfTextBoxExt
4)
FolderName : ViewModel
File : HomeViewModel.cs
--standard view model with Model as property with Validation triggering.
--Here if i put breakpoint on validation method i can see that error returned is correct but somehow it is not triggering the UI to show the error message.
--I've already set ValidatesOnNotifyDataError=true while binding the property with textbox.
Please check and guide me if something is wrong.
NOTE : I am using syncfusion controls but I tried with standard text box also, it is not triggering the errorchanged event.
Also I've used IntoifyDataError implementation from: https://www.thetechgrandma.com/2017/05/wpf-prism-inotifydataerrorinfo-and.html
--ValidatedPropertyChangedBase
public abstract class ValidatedPropertyChangedBase : PropertyChangedBase, INotifyDataErrorInfo
{
private readonly Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public void SetError(string propertyName, string errorMessage)
{
if (!_errors.ContainsKey(propertyName))
_errors.Add(propertyName, new List<string> { errorMessage });
RaiseErrorsChanged(propertyName);
}
protected void ClearError(string propertyName)
{
if (_errors.ContainsKey(propertyName))
_errors.Remove(propertyName);
RaiseErrorsChanged(propertyName);
}
protected void ClearAllErrors()
{
var errors = _errors.Select(error => error.Key).ToList();
foreach (var propertyName in errors)
ClearError(propertyName);
}
public void RaiseErrorsChanged(string propertyName)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { return; };
public bool HasErrors
{
get { return _errors.Any(x => x.Value != null && x.Value.Count > 0); }
}
public IEnumerable GetErrors(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) ||
!_errors.ContainsKey(propertyName)) return null;
return _errors[propertyName];
}
}
--ViewModel
public class HomeViewModel : ValidatedPropertyChangedBase
{
private TestModel _model;
public TestModel Model
{
get { return _model; }
set
{
if (_model != value)
{
_model = value;
NotifyOfPropertyChange(() => Model);
}
}
}
public HomeViewModel()
{
Model = new TestModel();
}
public void ValidateData()
{
ClearAllErrors();
var validator = new TestModelValidation();
FluentValidation.Results.ValidationResult result = validator.Validate(Model);
foreach (var error in result.Errors)
{
SetError(error.PropertyName, error.ErrorMessage);
}
if (result.IsValid)
{
MessageBox.Show("Data good to save !");
}
}
}
--View
<Window
x:Class="ValidationDemo.Views.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:convertor="clr-namespace:ValidationDemo.Infrastructure"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ValidationDemo.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sf="http://schemas.syncfusion.com/wpf"
Title="HomeView"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.Resources>
<Style
TargetType="sf:SfTextInputLayout">
<Setter Property="Width" Value="200" />
</Style>
</Window.Resources>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Vertical">
<sf:SfTextInputLayout
Hint="First Name">
<sf:SfTextBoxExt
Text="{Binding Path=Model.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
</sf:SfTextInputLayout>
<sf:SfTextInputLayout
Hint="Last Name">
<sf:SfTextBoxExt
Text="{Binding Path=Model.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
</sf:SfTextInputLayout>
<TextBox
Text="{Binding Path=Model.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
<TextBox
Text="{Binding Path=Model.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
<Button
x:Name="ValidateData"
Content="Validate Data" />
</StackPanel>
--Model with Fluent validation implemented:
public class TestModel : ValidatedPropertyChangedBase
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName != value)
{
_firstName = value;
NotifyOfPropertyChange(() => FirstName);
}
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
if (_lastName != value)
{
_lastName = value;
NotifyOfPropertyChange(() => LastName);
}
}
}
}
public class TestModelValidation:AbstractValidator<TestModel>
{
public TestModelValidation()
{
RuleFor(t => t.FirstName)
.NotEmpty()
.WithMessage("Please enter first name");
RuleFor(t => t.FirstName)
.NotEmpty()
.WithMessage("Please enter last name");
}
}
Was able to figure out the problem, was with control exposing the HasError from view Model.
ErrorText="{Binding RelativeSource={RelativeSource Mode=Self}, Path=InputView.(Validation.Errors), Converter={StaticResource EC}}"
HasError="{Binding RelativeSource={RelativeSource Mode=Self}, Path=InputView.(Validation.Errors), Converter={StaticResource ECO}}"
Added the properties on the control with convertors and it fixed the issue.
I need to have a combobox with two values. The first should have a custom name, while the second should use the underlying bound object's properties. Both items are values on the VM, and I'm able to bind all of it successfully.
XAML
<Window x:Class="StaticComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StaticComboBox"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:StaticUIVm}"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox Grid.Row="1"
SelectedValuePath="Tag"
SelectedValue="{Binding SelectedValue, Mode=TwoWay}">
<ComboBox.Items>
<ComboBoxItem Content="Custom Display Text 111"
Tag="{Binding FirstValue}" />
<ComboBoxItem Content="{Binding SecondValue.Item2}"
Tag="{Binding SecondValue}" />
</ComboBox.Items>
</ComboBox>
</Grid>
</Window>
XAML.cs
using System.Windows;
namespace StaticComboBox
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new StaticUIVm();
}
}
}
StaticUIVm.cs
using StaticComboBox.Annotations;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace StaticComboBox
{
public class StaticUIVm : INotifyPropertyChanged
{
public Tuple<long, string> FirstValue { get; set; }
public Tuple<long, string> SecondValue { get; set; }
private Tuple<long, string> _selectedValue;
public Tuple<long, string> SelectedValue
{
get { return _selectedValue; }
set
{
_selectedValue = value;
OnPropertyChanged();
}
}
public StaticUIVm()
{
FirstValue = new Tuple<long, string>(1, "Some Static Value");
SecondValue = new Tuple<long, string>(2, "Some Other Static Value");
SelectedValue = FirstValue;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
My problem is that despite the bindings working correctly for the items and displaying and when I as a user select a value, the combobox isn't reflecting the correct selection when initializing the VM class. Meaning, it doesn't select FirstValue. This doesn't make sense to me as the reference should be exactly the same, and I've confirmed that the value is in fact changing on the VM during initialization. I've definitely initialized values in the constructor and had them respected and displayed on load, so I'm a little confused as to where I'm going wrong here.
EDIT
I've accepted mm8's answer, but had to make a few additional tweaks to the XAML to get it to behave as needed. I needed to be able to trigger the custom text based on the ID value of the items, which was set at run time. Because of this a simple DataTrigger would not work so I had to use a MultiBinding. The MultiBinding broke the display when an item was selected (as described in ComboBox.ItemTemplate not displaying selection properly) so I had to set IsEditable to false. The full combobox is below.
<ComboBox Grid.Row="2"
Grid.Column="1"
IsEditable="False"
ItemsSource="{Binding ItemSource}"
SelectedItem="{Binding SelectedValue}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text"
Value="{Binding Name}" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource LongEqualToLongMultiBindingDisplayConverter}">
<Binding Path="Id" />
<Binding Path="DataContext.FirstValue.Id" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Text"
Value="Custom Display Text 111" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This XAML in combination with the suggestions from mm8's answer (setting up a collection which is initialized at runtime from the two provided values) did the trick.
Why don't you simply expose a collection of selectable items from your view model? This is how to solve this using MVVM:
public class StaticUIVm : INotifyPropertyChanged
{
public Tuple<long, string> FirstValue { get; set; }
public Tuple<long, string> SecondValue { get; set; }
private Tuple<long, string> _selectedValue;
public Tuple<long, string> SelectedValue
{
get { return _selectedValue; }
set
{
_selectedValue = value;
OnPropertyChanged();
}
}
public IEnumerable<Tuple<long, string>> Values { get; }
public StaticUIVm()
{
FirstValue = new Tuple<long, string>(1, "Some Static Value");
SecondValue = new Tuple<long, string>(2, "Some Other Static Value");
Values = new Tuple<long, string>[2] { FirstValue, SecondValue };
SelectedValue = SecondValue;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<ComboBox x:Name="cmb" Grid.Row="1" ItemsSource="{Binding Values}"
SelectedItem="{Binding SelectedValue}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Item2}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Item1}" Value="1">
<Setter Property="Text" Value="Custom Display Text 111" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You might even remove the FirstValue and SecondValue properties. The custom text is defined in the view but the actual options to choose from is defined in the view model.
MAJOR EDITS:
So when I first added an answer, I was trying to use what you already had instead of demonstrating how I would do it. WPF is both flexible and constrictive in certain ways and I have often found myself working around problems. Your question is actually quite simple when done using a different approach. Many of my programs have ComboBox controls and although I normally populate with a collection, a similar principle can be applied by using a helper data class over a Tuple. This will add significantly more flexibility and be more robust.
I added the property SelectedIndex and bound it to an int in your datacontext class. I also changed SelectedValue to SelectedItem as it it far superior in this use case.
<ComboBox Grid.Row="1"
SelectedValuePath="Tag"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}">
<ComboBox.Items>
<ComboBoxItem Content="{Binding FirstValue.display}"
Tag="{Binding FirstValue}" />
<ComboBoxItem Content="{Binding SecondValue.display}"
Tag="{Binding SecondValue}" />
</ComboBox.Items>
</ComboBox>
Datacontext Class, Data Class, and Extension Method:
So, I moved your property changed event over to a separate class. I recommend doing this as it makes it reusable. It is especially handy for the Data Class. Now in the constructor we set the selected item AND the selected index.
public class StaticUIVm : PropertyChangeHelper
{
private ComboBoxDataType _FirstValue;
public ComboBoxDataType FirstValue
{
get { return _FirstValue; }
set
{
_FirstValue = value;
OnPropertyChanged();
}
}
private ComboBoxDataType _SecondValue { get; set; }
public ComboBoxDataType SecondValue
{
get { return _SecondValue; }
set
{
_SecondValue = value;
OnPropertyChanged();
}
}
private ComboBoxDataType _SelectedItem;
public ComboBoxDataType SelectedItem
{
get { return _SelectedItem; }
set
{
_SelectedItem = value;
OnPropertyChanged();
}
}
private int _SelectedIndex;
public int SelectedIndex
{
get { return _SelectedIndex; }
set
{
_SelectedIndex = value;
OnPropertyChanged();
}
}
public StaticUIVm(string dynamicName)
{
FirstValue = new ComboBoxDataType() { id = 1, data = "Some Static Value", display = "Custom Display Text 111", };
SecondValue = new ComboBoxDataType() { id = 2, data = dynamicName, display = dynamicName, };
SelectedItem = FirstValue;
SelectedIndex = 0;
}
}
public class ComboBoxDataType : PropertyChangeHelper
{
private long _id { get; set; }
public long id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
private string _data { get; set; }
public string data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged();
}
}
private string _display { get; set; }
public string display
{
get { return _display; }
set
{
_display = value;
OnPropertyChanged();
}
}
}
public class PropertyChangeHelper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The reason for all of this you might ask...well this adds for flexibility instead of "hacking" things or adding in extra complexity in the XAML with a data trigger. You are working purely off of logic using an easy to manipulate data class.
I am trying to create a TreeView for my application. This is the first time I am using TreeView with the MVVM structure, by all accounts the binding is working and displaying correctly.
However:
How do I get the selection so I can perform some logic after the user selects something?
I thought that the TextValue property in SubSection class would fire PropertyChanged, but it doesn't, so I am left scratching my head.
This is the most simplified set of code I could make for this question:
Using PropertyChanged setup like this in : ViewModelBase class
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
The VeiwModel:
public class ShiftManagerViewModel : ViewModelBase
{
public ShiftManagerViewModel()
{
Departments = new List<Department>()
{
new Section("Section One"),
new Section("Section Two")
};
}
private List<Section> _sections;
public List<Section> Sections
{
get{return _sections;}
set
{
_sections = value;
NotifyPropertyChanged();
}
}
}
The classes:
public class Section : ViewModelBase
{
public Section(string depname)
{
DepartmentName = depname;
Courses = new List<SubSection>()
{
new SubSection("SubSection One"),
new SubSection("SubSection One")
};
}
private List<SubSection> _courses;
public List<SubSection> Courses
{
get{ return _courses; }
set
{
_courses = value;
NotifyPropertyChanged();
}
}
public string DepartmentName { get; set; }
}
public class SubSection : ViewModelBase
{
public SubSection(string coursename)
{
CourseName = coursename;
}
public string CourseName { get; set; }
private string _vTextValue;
public string TextValue
{
get { return _vTextValue; }
set
{
_vTextValue = value;
NotifyPropertyChanged();
}
}
}
And the XAML:
<Window.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Courses}" DataType="{x:Type viewModels:Section}">
<Label Content="{Binding DepartmentName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding TextValue}" DataType="{x:Type viewModels:SubSection}">
<Label Content="{Binding CourseName}" />
</HierarchicalDataTemplate>
</Window.Resources>
Could someone point me in the right direction?
You could cast the SelectedItem property of the TreeView to a Section or a SubSection or whatever the type of the selected item is:
Section section = treeView1.SelectedItem as Section;
if (section != null)
{
//A Section is selected. Access any of its properties here
string name = section.DepartmentName;
}
else
{
SubSection ss = treeView1.SelectedItem as SubSection;
if(ss != null)
{
string ssName = ss.CourseName;
}
}
Or you could add an IsSelected property to the Section and SubSection types and bind the IsSelected property of the TreeView to it:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
Then you get the selected item by iterating through the ItemsSource of the TreeView and look for the item that has the IsSelected source property set to true.
EDITED: As comments have suggested, I should implement the MVVM pattern, I have done exactly that. However the same problem still persists. So I have altered the question accordingly:
I have a datagrid containing two columns bound to an observable collection (in the MyNotes class). One column contains a combobox and the other a textbox. The collection stores references to Note objects that contain an enumeration variable (displayed by the combobox) and a string (displayed by the textbox). All works fine except for the SelectedItems (and therefore the SelectedItem). When the program is built and run, you can add new rows to the datagrid (using the add/remove buttons) but after you attempt an edit (by entering the datagrid's textbox or combobox) then the datagrid's selectedItems and selectedItem fail. This can be seen by the use of the add/remove buttons: the selected row is not deleted and a new row is not added above the selected row respectively. This is a result of a symptom regarding the SelectedNote property losing its binding (I don't know why this happens and when I attempt to hack a rebind, the rebind fails?). Another symptom relates to the selected items property not reflecting what the datagrid is actually showing as selected (when viewing it in debug mode).
I am sure this problem relates to an issue with the datagrid, which makes it unusable for my case.
Here is the new XAML (its datacontext, the viewmodel, is set in the XAML and ParaTypes and headerText are both XAML static resources):
<DataGrid x:Name ="dgdNoteLimits"
ItemsSource ="{Binding ParagraphCollection}"
SelectedItem ="{Binding Path=SelectedNote, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AllowDrop ="True"
HeadersVisibility ="Column"
AutoGenerateColumns ="False"
CanUserAddRows ="False"
CanUserReorderColumns ="False"
CanUserSortColumns ="False"
BorderThickness ="0"
VerticalGridLinesBrush ="DarkGray"
HorizontalGridLinesBrush="DarkGray"
SelectionMode ="Extended"
SelectionUnit ="FullRow"
ColumnHeaderStyle ="{StaticResource headerText}">
<DataGrid.ItemContainerStyle>
<Style>
<Style.Resources>
<!-- SelectedItem's background color when focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Blue"/>
<!-- SelectedItem's background color when NOT focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Blue" />
</Style.Resources>
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.Columns>
<DataGridComboBoxColumn Header = "Note Type"
ItemsSource = "{Binding Source={StaticResource ParaTypes}}"
SelectedValueBinding= "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBinding = "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth = "115"
Width = "Auto">
</DataGridComboBoxColumn>
<DataGridTextColumn Header ="Description"
Binding="{Binding Path=NoteText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width ="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="SpellCheck.IsEnabled"
Value ="true" />
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1"
Margin="0, 0, 0, 16"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Add"
Width="72"
Margin="16,8,8,8"
Command="{Binding AddClickCommand}"/>
<Button Content="Remove"
Width="72"
Margin="16,8,8,8"
Command="{Binding RemoveClickCommand}"/>
</StackPanel>
Here is the view model:
class MainWindowViewModel : INotifyPropertyChanged
{
MyNotes NotesCollection;
private bool canExecute;
private ICommand clickCommand;
public MainWindowViewModel()
{
this.NotesCollection = new MyNotes();
this.ParagraphCollection = this.NotesCollection.Notes;
this.canExecute = true;
}
private ObservableCollection<Note> paragraphCollection;
public ObservableCollection<Note> ParagraphCollection
{
get { return this.paragraphCollection; }
set
{
this.paragraphCollection = value;
RaisePropertyChanged(() => this.ParagraphCollection);
}
}
private Note selectedNote;
public Note SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
RaisePropertyChanged(() => this.SelectedNote);
}
}
public ICommand AddClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => AddButtonHandler(), canExecute));
}
}
public void AddButtonHandler()
{
int noteIndex = 0;
Note aNote;
// what to do if a note is either selected or unselected...
if (this.SelectedNote != null)
{
// if a row is selected then add row above it.
if (this.SelectedNote.NoteIndex != null)
noteIndex = (int)this.SelectedNote.NoteIndex;
else
noteIndex = 0;
//create note and insert it into collection.
aNote = new Note(noteIndex);
ParagraphCollection.Insert(noteIndex, aNote);
// Note index gives sequential order of collection
// (this allows two row entries to have same NoteType
// and NoteText values but still note equate).
int counter = noteIndex;
// reset collection index so they are sequential
for (int i = noteIndex; i < this.NotesCollection.Notes.Count; i++)
{
this.NotesCollection.Notes[i].NoteIndex = counter++;
}
}
else
{
//if a row is not selected add it to the bottom.
aNote = new Note(this.NotesCollection.Count);
this.ParagraphCollection.Add(aNote);
}
}
public ICommand RemoveClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => RemoveButtonHandler(), canExecute));
}
}
public void RemoveButtonHandler()
{
//delete selected note.
this.ParagraphCollection.Remove(selectedNote);
}
//boiler plate INotifyPropertyChanged implementation!
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged<T>(Expression<System.Func<T>> propertyExpression)
{
var memberExpr = propertyExpression.Body as MemberExpression;
if (memberExpr == null)
throw new ArgumentException("propertyExpression should represent access to a member");
string memberName = memberExpr.Member.Name;
RaisePropertyChanged(memberName);
}
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and the model:
class MyNotes
{
public ObservableCollection<Note> Notes;
public MyNotes()
{
this.Notes = new ObservableCollection<Note>();
}
}
public enum NoteTypes
{
Header, Limitation, Warning, Caution, Note
}
public class Note
{
public int? NoteIndex { get; set; }
public NoteTypes NoteType { get; set; }
public string NoteText { get; set; }
public Note()
{
this.NoteIndex = null;
this.NoteType = NoteTypes.Note;
this.NoteText = "";
}
public Note(int? noteIndex): this()
{
this.NoteIndex = noteIndex;
}
public override string ToString()
{
return this.NoteType + ": " + this.NoteText;
}
public override bool Equals(object obj)
{
Note other = obj as Note;
if (other == null)
return false;
if (this.NoteIndex != other.NoteIndex)
return false;
if (this.NoteType != other.NoteType)
return false;
if (this.NoteText != other.NoteText)
return false;
return true;
}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 23 + this.NoteIndex.GetHashCode();
hash = hash * 23 + this.NoteType.GetHashCode();
hash = hash * 23 + this.NoteText.GetHashCode();
return hash;
}
}
Existing comments were greatly appreciated (I have learnt a lot and see the value of MVVM). It is just a shame they have not resolved the problem. But thank you.
So if anyone knows how I can resolve this issue, then that would be greatly appreciated.
how to bind Dictionary to ListView and Text box?
namespace Models
{
public class DynamicList
{
#region Constructor
public DynamicList()
{
_propDict = new Dictionary<string, object>();
dynimicListProps = new List<DynimicListProperty>();
}
#endregion Constructor
#region Fields
private Dictionary<string, object> _propDict;
private IList<DynimicListProperty> dynimicListProps;
#endregion Fields
#region Properties
public Dictionary<string, object> PropsDict
{
get { return _propDict; }
set { _propDict = value; }
}
public string TestString
{
get { return "Hello! It's works!"; }
}
#endregion Properties
#region Methods
public void CreateProperties(string[] arrLine)
{
for (int i = 0; i < arrLine.Count(); i++)
{
_propDict.Add(arrLine[i].Replace("\"",""), null);
}
}
#endregion Methods
}
public class DynimicListProperty
{
private IList<string> propertyNameValues = new List<string>();
public IList<string> PropertyNameValues
{
get { return propertyNameValues; }
set { propertyNameValues = value; }
}
}
}
// now try to bind
private Models.DynamicList _dynimicList;
public Models.DynamicList _DynimicList
{
get { return _dynimicList; }
}
CreateView()
{
_importView = new Views.ImportBomView();
_importView.Grid1.DataContext = _DynimicList;
Binding bn = new Binding("Value.[2]");
bn.Mode = BindingMode.OneWay;
bn.Source = _DynimicList.PropsDict.Keys;
_importView.tbFileName.SetBinding(TextBlock.TextProperty, bn);
/////////////////////////////////////////////////////////////////////////////
//_importView.listView1.ItemsSource = (IEnumerable)_DynimicList.PropsDict["Value"];
////// it's works when Binding bn2 = new Binding("") but of course in
///this emplementation I have the same data in all columns - so not good
/////////////////////////////////////////////////////////////////////////////////////
// here I'll like to generate Columns and bind gvc.DisplayMemberBinding
// to dictionary _DynimicList.PropsDict[item] with Key=item
foreach (var item in _DynimicList.PropsDict.Keys)
{
Binding bn2 = new Binding("[3]");
bn2.Source = (IEnumerable)_DynimicList.PropsDict[item];
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = bn2;
gvc.Header = item;
gvc.Width = 100;
_importView.gridView1.Columns.Add(gvc);
}
_importView.Show();
}
}
You can write a datatemplate for KeyValuePair<string, List<string>> and put it in the ItemsTemplate of the root ListView. The ItemsSource of your ListView would be your dictionary. In this datatemplate you would have another itemscontrol (such as another listview) where you set the itemstemplate to a textbox that binds to the string. Alternatively you could use a single TreeView for everything in conjunction with hierarchical datatemplate. You can make a TreeView look however you want using templates.
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ContentPresenter Content="{Binding Key}" Margin="0 0 4 0"/>
<ItemsControl ItemsSource="{Binding Value}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Margin" Value="0 0 2 0" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in the code behind some sample data:
var data = new Dictionary<string, List<string>>
{
{"1", new List<string> {"one", "two", "three", "four"}},
{"2", new List<string> {"five", "six", "seven", "eight"}}
};
this.listBox.ItemsSource = data;