Hi i want to Bind to an "unknown" (i only get a string) Property in Xaml
at first i wrote an IValueConverter but you can't bind to ConverterParameter
so i rewrite it as IMultiValueConverter but now i'm unable to figure out how to use the <Binding /> with out Path
or my i'm wrong?
if you write <TextBlock Text="{Binding}" /> you will get the object Person
and with {Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}, Path=View.Columns[0].Header}} i'm able to access the Header Text of the first row
now i'm only need to combine both and a will get the Property right?
my test Xaml code:
<UserControl x:Class="Frameworktest.View.auswahl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Frameworktest">
<UserControl.Resources>
<local:toPropertyConverter x:Key="PropertyConverter"/>
</UserControl.Resources>
<StackPanel>
<!--...-->
<Border BorderThickness="5" HorizontalAlignment="Left" VerticalAlignment="Top"
BorderBrush="Green" CornerRadius="5">
<ListView Name="listView1" IsSynchronizedWithCurrentItem="False"
ItemsSource="{Binding Items, UpdateSourceTrigger=PropertyChanged}" <!--ObservableCollection<Person>-->
SelectedItem="{Binding selectedItem, UpdateSourceTrigger=PropertyChanged}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="1">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource PropertyConverter}">
<Binding /><!-- How do i {Binding} here?-->
<Binding Source="{Binding RelativeSource={Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}, Path=View.Columns[0].Header}}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Firstname" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="1" Text="{Binding Path=Name}" Width="100"/><!--works-->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Age">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="1" Text="{Binding Age}" Width="50"/><!--works-->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Border>
</StackPanel>
</UserControl>
the Converter:
public class toPropertyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].GetType().GetProperty((string)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The Model
public class Person : MBase, IContains
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value;
RaisePropertyChanged(() => Reg(() => Name));
}
}
private string _firstname;
public string Firstname
{
get { return _firstname; }
set
{
_firstname = value;
RaisePropertyChanged(() => Reg(() => Firstname));
}
}
private int _age;
public int Age
{
get { return _age; }
set
{
_age = value;
RaisePropertyChanged(() => Reg(() => Age));
}
}
public bool Contains(string text)
{
string pers = string.Format("{0} {1}", Firstname, Name);
return pers.Contains(text);
}
}
Update my current Multibindung
<MultiBinding Converter="{StaticResource PropertyConverter}">
<Binding Path="."/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}" Path="View.Columns[0].Header}}" /> <!-- doesn't contain the word "Name" like i suspected -->
</MultiBinding>
LAST Update
it is a dead end in my case you can't Bind from the GridViewColumn.CellTemplate to the specific Column Header Value
{Binding} implicitely means : {Binding Path=.}.
So you could use
<Binding Path="."/>
Related
When the cell in the column is in focus the ComboBox shall appear but once the value is selected and the cell is not in focus anymore, only the text shall appear. So the ComboBox shall only be visible when cell is in focus.
This is my code but I've really no clue how to solve that.
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="SchichtID" Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="SelectedShiftHID"
SelectedIndex="{Binding SchichtID}"
DisplayMemberPath="Bezeichnung"
ItemsSource="{Binding DataContext.UiShiftHModelList, Mode=OneWay,RelativeSource={RelativeSource AncestorType=ListView},UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
1.EDIT:
What I'm trying here is to put combobox into a column of a ListView. The values published there come from Model A. The DisplayedMemberPath is the description of the row from model a. We save the ID of that row from Model A in Model B. When the data is reloaded the correct description shall be loaded and shown again in the way explained in my initial post.
2.EDIT:
#Anton (that guy deleted his answer?) - your answer doesn't work. It starts that there is no comboBox shown when focussing the cell neither it shows any text.
In the XAML of the View im introducing the converters:
<UserControl.Resources>
<helpers:LastRowVisibilityMultiValueConverter x:Key="LastRowVisibilityMultiValueConverter" />
<helpers:ShiftHIDtoDescriptionConverter x:Key="ShiftHIDtoDescriptionConverter" ShiftH="{Binding DataContext.UiShiftHModelList, Mode=OneWay, ElementName=ShiftT, UpdateSourceTrigger=PropertyChanged}"/>
<helpers:CellTemplateSelector x:Key="cellTemplateSelector" x:Name="cellTemplateSelector">
<helpers:CellTemplateSelector.EditableTemplate>
<DataTemplate>
<ComboBox x:Name="SelectedShiftHID"
SelectedIndex="{Binding ID}"
DisplayMemberPath="Bezeichnung"
ItemsSource="{Binding UiShiftHModelList, Mode=OneWay,ElementName=ShiftT,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</helpers:CellTemplateSelector.EditableTemplate>
<helpers:CellTemplateSelector.ReadOnlyTemplate>
<DataTemplate>
<TextBlock Text="{Binding SchichtID, Converter={StaticResource ShiftHIDtoDescriptionConverter}}"/>
</DataTemplate>
</helpers:CellTemplateSelector.ReadOnlyTemplate>
</helpers:CellTemplateSelector>
</UserControl.Resources>
There simply happens nada.
One error I've got in your suggested converter was:
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(IEnumerable), typeof(ItemIdToStringConverter:DependencyObject), new PropertyMetadata(null));
This here: typeof(ItemIdToStringConverter:DependencyObject)
Following the adjusted converter:
public class ShiftHIDtoDescriptionConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty ShiftHProperty = DependencyProperty.Register("ShiftH", typeof(IEnumerable), typeof(ShiftHIDtoDescriptionConverter),new PropertyMetadata(null));
public IEnumerable ShiftH
{
get { return (IEnumerable)GetValue(ShiftHProperty); }
set { SetValue(ShiftHProperty, value); }
}
public object Convert(object shiftHID, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
int? id = shiftHID as int?;
if (id != null) {
return ShiftH.Cast<UiShiftHModel>().FirstOrDefault(m => m.ID == id)?.Bezeichnung;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
This here is the XAML part:
<Border Grid.Row="1" Grid.Column="1"
Margin="10,10,10,10"
BorderBrush="#FF474A57"
CornerRadius="10,10,10,10"
BorderThickness="2,2,2,2"
Width="520"
MaxHeight="300"
Background="White">
<StackPanel Margin="0,0,0,20" Orientation="Vertical">
<StackPanel Grid.Column="0" Grid.RowSpan="1"
Grid.Row="1"
VerticalAlignment="Top">
<Label HorizontalAlignment="Center" FontWeight="Bold">
Schichtdetails
</Label>
<ListView x:Name="ShiftT" MinHeight="150" MaxHeight="200" MinWidth="500" HorizontalContentAlignment="Stretch" HorizontalAlignment="Center"
ItemContainerStyle="{DynamicResource DifAlternationColorsLV}"
AlternationCount="2"
ItemsSource="{Binding UiShiftTModelList, UpdateSourceTrigger=PropertyChanged}" d:ItemsSource="{d:SampleData ItemCount=5}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="ID" Width="30">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="ID" MinWidth="30"
Style="{StaticResource TBoxInListV}"
Text="{Binding ID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
BorderThickness="0">
</TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="SchichtID" Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl ContentTemplateSelector="{StaticResource cellTemplateSelector}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</StackPanel>
</Border>
Watching the CellTemplateSelector with a breakpoint shows:
public class CellTemplateSelector : DataTemplateSelector
{
//Answer for question: switch appearance of the ListView column from combobox to textbox
//https://stackoverflow.com/questions/73046926/wpf-column-in-listview-shall-represent-a-combobox-when-isfocused-true-but-a-si/73048416?noredirect=1#comment129022042_73048416
public DataTemplate EditableTemplate { get; set; }
public DataTemplate ReadOnlyTemplate { get; set; }
public override DataTemplate
SelectTemplate(object item, DependencyObject container) {
ContentControl contentControl = container as ContentControl;
if (contentControl != null) {
if (contentControl.IsFocused)
return EditableTemplate;
else
return ReadOnlyTemplate;
}
return null;
}
that the contentControl is always null.
3.EDIT
I guess its not a ContentControl its rather a ContenPresenter. Then the casting works. But now I'm fucked up with Binding Errors:
4.EDIT
Oh, there is another problem with the converter for the id to description. The code therefor from a yet deleted answer is completely bs. The passed id has to be looked up in the UiShiftHModel but there is no chance to pass the collection into the converter. Maybe via multi binding converter....
First of all.. better to create own customControl with properties which allow you to switch templates for Readonly and Editable templates
public class InteractiveContentControl : ContentControl
{
public static readonly DependencyProperty IsEditableProperty =
DependencyProperty.Register("IsEditable", typeof(bool), typeof(InteractiveContentControl), new FrameworkPropertyMetadata(false, OnIsEditablePropertyChanged));
public bool IsEditable
{
get { return (bool)GetValue(IsEditableProperty); }
set { SetValue(IsEditableProperty, value); }
}
private static void OnIsEditablePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as InteractiveContentControl;
control.ChangeTemplate();
}
public static readonly DependencyProperty EditableTemplateProperty = DependencyProperty.Register("EditableTemplate", typeof(DataTemplate), typeof(InteractiveContentControl), new PropertyMetadata(null));
public DataTemplate EditableTemplate
{
get { return (DataTemplate)GetValue(EditableTemplateProperty); }
set { SetValue(EditableTemplateProperty, value); }
}
public static readonly DependencyProperty ReadonlyTemplateProperty = DependencyProperty.Register("ReadonlyTemplate", typeof(DataTemplate), typeof(InteractiveContentControl), new PropertyMetadata(null));
public DataTemplate ReadonlyTemplate
{
get { return (DataTemplate)GetValue(ReadonlyTemplateProperty); }
set { SetValue(ReadonlyTemplateProperty, value); }
}
public InteractiveContentControl():base()
{
DefaultStyleKey = typeof(ContentControl);
this.Loaded += OnLoaded;
this.LostFocus += OnLostFocus;
this.IsKeyboardFocusWithinChanged += InteractiveContentControl_IsKeyboardFocusWithinChanged;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
ChangeTemplate();
}
private void InteractiveContentControl_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
IsEditable = IsKeyboardFocusWithin;
}
private void OnLostFocus(object sender, RoutedEventArgs e)
{
IsEditable = IsKeyboardFocusWithin;
}
private void ChangeTemplate()
{
ContentTemplate = IsEditable ? EditableTemplate : ReadonlyTemplate;
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
IsEditable = true;
}
}
Also need to have class for convert Id to the Name from the comboBox.
public class ShiftHIDtoDescriptionConverter : DependencyObject, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
int? id = (int?)values[0];
IEnumerable<UiShiftHModel> items = values[1] as IEnumerable<UiShiftHModel>;
if (id!=null && items!=null)
{
return items.FirstOrDefault(i => i.ID == id)?.Bezeichnung;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then you can define your resources in xaml
<UserControl.Resources>
<helpers:ShiftHIDtoDescriptionConverter x:Key="ShiftHIDtoDescriptionConverter" />
<DataTemplate x:Key="EditableTemplate" DataType="UIShiftTModel">
<ComboBox x:Name="SelectedShiftHID"
ItemsSource="{Binding DataContext.UiShiftHModelList, Mode=OneWay, ElementName=ShiftT, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="ID"
SelectedValue="{Binding SchichID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Bezeichnung"/>
</DataTemplate>
<DataTemplate x:Key="ReadonlyTemplate" DataType="UIShiftTModel">
<Grid>
<TextBlock HorizontalAlignment="Stretch">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ShiftHIDtoDescriptionConverter}">
<Binding Path="SchichID" />
<Binding Path="DataContext.UiShiftHModelList" ElementName="ShiftT" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
</UserControl.Resources>
And then you will be able to define your GridView
<ListView x:Name="ShiftT" MinHeight="150"
AlternationCount="2"
ItemsSource="{Binding UiShiftTModelList, UpdateSourceTrigger=PropertyChanged}" d:ItemsSource="{d:SampleData ItemCount=5}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="ID" Width="30">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="ID" MinWidth="30"
Text="{Binding ID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="SchichtID" x:Name="colSchichtID">
<GridViewColumn.CellTemplate>
<DataTemplate>
<helpers:InteractiveContentControl Content="{Binding}" Width="200"
EditableTemplate="{StaticResource EditableTemplate}"
ReadonlyTemplate="{StaticResource ReadonlyTemplate}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
I've nested menuitems bounded to observable collection named 'CollectionOfAuthors'.
Here's the MenuItem Hierarchy:
Author -->AuthorName1-->BookName1,BookName2,BookName3
Author is TopLevelMenuItem which opens in list of Author names such that each Author name opens into list of Books.
While Clicking on each BookName menuitem through NavigateToBook command, I want to send the BookName, AuthorName and AuthorID to ViewModel as command parameters,
But I am finding empty values as (DependencyProperty.UnsetValue) passed to ViewModel.
Need to know what correction is required?
View.xaml
<Menu>
<MenuItem Header="Authors" x:Name="TopLevelMenuItem"
ItemsSource="{Binding CollectionOfAuthors, Mode=TwoWay}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="PreviewMouseLeftButtonDown">
<in:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList,RelativeSource={RelativeSource AncestorType=Menu}}"/>
</in:EventTrigger>
</in:Interaction.Triggers>
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<StackPanel>
<TextBlock x:Name="tbAuthor" Text="{Binding AuthorName}"/>
<TextBlock x:Name="tbAuthorID" Text="{Binding AuthorID}" Visibility="Collapsed"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tbBookName" Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" MouseAction="LeftClick" >
<MouseBinding.CommandParameter>
<MultiBinding Converter="{StaticResource MultiCommandConverter}">
<Binding Path="Text" ElementName="tbBookName"/>
<Binding Path="DataContext.AuthorName" RelativeSource="{RelativeSource AncestorLevel=2, AncestorType=MenuItem}" />
<Binding Path="DataContext.AuthorID" RelativeSource="{RelativeSource AncestorLevel=2, AncestorType=MenuItem}" />
</MultiBinding>
</MouseBinding.CommandParameter>
</MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
ViewModel.cs
public ICommand NavigateToBook
{
get { return new DelegateCommand(NavigateToBookExecute); }
}
private void NavigateToBookExecute(object obj)
{
string selectedBookName = ((object[])obj)[0].ToString();
string selectedAuthorName = ((object[])obj)[1].ToString();
string selectedAuhorID = ((object[])obj)[2].ToString();
}
public ICommand RefreshAuthorsList
{
get { return new DelegateCommand(RefreshAuthorsListExecute); }
}
private void RefreshAuthorsListExecute(object m)
{
CollectionOfAuthors = new ObservableCollection<Author>();
//Here AuthorDetails is another global collection which gets loaded during constructor call
foreach (var objAuthorItem in AuthorDetails)
{
CollectionOfAuthors.Add(new Author
{
AuthorName = objAuthorItem.DisplayName,
Books = objAuthorItem.ListOfBooks,
AuthorID = objAuthorItem.Id,
});
}
}
private ObservableCollection<Author> _collectionOfAuthors;
public ObservableCollection<Author> CollectionOfAuthors
{
get { return _collectionOfAuthors; }
set { SetProperty(ref _collectionOfAuthors, value); }
}
Author.cs
public class Author
{
public string AuthorName { get; set; }
public string AuthorID { get; set; }
List<string>Books = new List<string>();
}
MultiCommandConverter.cs
public class MultiCommandConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Clone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Since you have Command at top level menu item, this Command will try to call even before your inner Command, no mather what event should trigger it.
As Workaround you could pass IsSubmenuOpen property of TopMenuItem as CommandParameter, and check if Menu is opened, and then in Command's execute action you could check if menu is Opened then continue or return. This will stop your items from being refreshed.
CallStack of your command is:
Click on book menuItem
RefreshListCommand runs
items are being refreshed, old ones are removed
Binding is trying to get properites from just removed items
Sample solution:
View.xaml
<Menu>
<MenuItem Header="Authors" Background="Red" x:Name="TopLevelMenuItem"
ItemsSource="{Binding CollectionOfAuthors, Mode=TwoWay}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="PreviewMouseLeftButtonDown">
<in:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList,RelativeSource={RelativeSource AncestorType=Menu}}" CommandParameter="{Binding IsSubmenuOpen , ElementName=TopLevelMenuItem}"/>
</in:EventTrigger>
</in:Interaction.Triggers>
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<StackPanel DataContext="{Binding}">
<TextBlock x:Name="tbAuthor" Text="{Binding AuthorName}"/>
<TextBlock x:Name="tbAuthorID" Text="{Binding AuthorID}" Visibility="Collapsed"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tbBookName" DataContext="{Binding}" Text="{Binding}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="MouseDown">
<in:InvokeCommandAction Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" >
<in:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiCommandConverter}">
<Binding Path="Text" ElementName="tbBookName"/>
<Binding Path="DataContext.AuthorName" RelativeSource="{RelativeSource AncestorLevel=1, AncestorType=StackPanel}" />
<Binding Path="DataContext.AuthorID" RelativeSource="{RelativeSource AncestorLevel=1, AncestorType=StackPanel}" />
</MultiBinding>
</in:InvokeCommandAction.CommandParameter>
</in:InvokeCommandAction>
</in:EventTrigger>
</in:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
And then in your RefreshAuthorsListExecute
private void RefreshAuthorsListExecuteExecute(object m)
{
if ((bool)m)
return;
I have a ListView with checkboxes like this:
<ListView
x:Name="usersListView"
Width="300"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="childrenListView_SelectionChanged"
Background="{StaticResource BackgroundPrimaryBrush}"
Foreground="{StaticResource WhiteBrush}"
Grid.Row="6" Grid.ColumnSpan="2"
>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Tag="{Binding Id}" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Path=IsSelected}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding FullName}" Header="Name" Width="250"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
All checkboxes in ListView are from List 'AllUsers' from database.
Now I want to set specific checkboxes to IsChecked=True in code behind.
I have another List 'Children' which have only few of the 'AllUsers' elements.
What I want is to display ListView with selected checkboxed binded to Persons in 'Children'.
I tried to implement this by myself with INotifyPropertyChanged implemented class wrapper to Person but I couldn't get Binding properly with this.
I hope I did explain the problem properly.
Thank you in advance :)
Consider using a IMultiValueConverter.
In the example below, my Children object is a simple string with the name. I have two list, the AllChildrens list and the SelectedChildrens list.
Foreach element in the AllChildrens collection, the converter checks if the element is contained into SelectedChildrens collection.
XAML: (I've removed the events)
<ListView ItemsSource="{Binding AllChildrens}" Tag="{Binding SelectedChildrens}">
<ListView.Resources>
<local:IEnumerableContainsConverter x:Key="Contains" />
</ListView.Resources>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Content="{Binding}">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource Contains}">
<Binding Path="." />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListView}}" Path="Tag" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding FullName}" Header="Name" Width="250"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
ViewModel:
public class Model
{
public Model()
{
AllChildrens = new List<string>()
{
"James",
"Annabelle",
"Kevin",
"William",
"Joseph",
};
SelectedChildrens = new List<string>()
{
"James",
"Annabelle",
"William",
};
}
public List<string> AllChildrens { get; set; }
public List<string> SelectedChildrens { get; set; }
}
Converter:
class IEnumerableContainsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null &&
values.Length == 2 &&
values[0] is string current_children && // Replace with your children object type
values[1] is IEnumerable<string> selected) // Replace with your children object type
{
return selected.Contains(current_children);
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have the following working code:
<StackPanel>
<TextBlock FontSize="14" Foreground="White" Text="Case Type: " TextDecorations="Underline"/>
<RadioButton IsChecked="{Binding CaseType, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeA}}"
Style="{StaticResource ToggleButtonStyle}"
Content="{Binding CaseType, Converter={StaticResource MyEnumDescriptionConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeA}}" />
<RadioButton IsChecked="{Binding CaseType, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeB}}"
Style="{StaticResource ToggleButtonStyle}"
Content="{Binding CaseType, Converter={StaticResource MyEnumDescriptionConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeB}}" />
...
...
...
<RadioButton IsChecked="{Binding CaseType, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeJ}}"
Style="{StaticResource ToggleButtonStyle}"
Content="{Binding CaseType, Converter={StaticResource MyEnumDescriptionConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeJ}}" />
</StackPanel>
Is there any way to do the same functionality without copy/paste :)
Ok without knowing your logic I cannot validate if you actually need two values going into the converter where 1 is the same for every item anyways.
However assuming you indeed do need them:
xaml:
<StackPanel>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Resources>
<local:MyEnumDescriptionConverter x:Key="MyEnumDescriptionConverter" />
<local:MyEnumToBooleanConverter x:Key="MyEnumToBooleanConverter" />
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton>
<RadioButton.Content>
<MultiBinding Converter="{StaticResource MyEnumDescriptionConverter}">
<Binding Path="." />
<Binding Path="DataContext.CaseType"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}" />
</MultiBinding>
</RadioButton.Content>
<RadioButton.IsChecked>
<MultiBinding Converter="{StaticResource MyEnumToBooleanConverter}">
<Binding Path="." />
<Binding Path="DataContext.CaseType"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}" />
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Starting from the Top:
Items is defined as:
public List<CaseTypeEnum> Items {
get {
return Enum.GetValues(typeof(CaseTypeEnum)).Cast<CaseTypeEnum>().ToList();
}
}
and
private CaseTypeEnum _caseType;
public CaseTypeEnum CaseType {
get {
return _caseType;
}
set {
if (value == _caseType)
return;
_caseType = value;
RaisePropertyChanged(() => CaseType);
}
}
enum:
public enum CaseTypeEnum{
TypeA,
TypeB,
TypeC,
TypeD,
TypeE,
TypeF,
TypeG,
TypeH,
TypeI,
TypeJ,
}
As for the two MultiBinding's, I just put some dummy code like
MyEnumDescriptionConverter -
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values.Length < 2)
return string.Empty;
return string.Format("Formatted {0} with CaseType property: {1}", (CaseTypeEnum)values[0], (CaseTypeEnum)values[1]);
}
and MyEnumToBooleanConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values.Length < 2)
return false;
return ((CaseTypeEnum)values[0]).ToString().EndsWith("D");
}
which should when run give you:
You can download the sample Here
I have a datagrid which has two combo box columns in it. The first combo box is a list of PersonnelTypes. Depending on which PersonnelType is selected, the second combo box should fill up with a list of Resources that match the selected PersonnelType
The problem is, lets say I have two rows of data, if I change the PersonnelType of one row, the datagrid will set the itemsource for all of the Resources in every row. I only want it to filter the row that I am in, not all the rows.
Here's the xaml for the part of the datagrid that has the combo boxes:
<DataGridTemplateColumn Header="Personnel Type" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelTypes" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" SelectionChanged="cmbPersonnelTypes_SelectionChanged" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelName" FontWeight="Bold" ItemsSource="{Binding ViewModel.ResourcesToChooseFrom, RelativeSource={RelativeSource AncestorType=Window},UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Resource, Mode=TwoWay}" SelectedValuePath="Refno" DisplayMemberPath="Name" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is the xaml for the whole data grid (just in case you need to see it):
<DataGrid AutoGenerateColumns="False" CanUserSortColumns="False" CanUserDeleteRows="True" IsReadOnly="True" Background="LightGray" CanUserAddRows="False" Margin="5" SelectedItem="{Binding SelectedLA_JobPersonnel}" ItemsSource="{Binding LA_Personnel}" Grid.ColumnSpan="4" MouseDoubleClick="DataGrid_MouseDoubleClick_1">
<DataGrid.Resources>
<ViewModels:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Personnel Type" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelTypes" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" SelectionChanged="cmbPersonnelTypes_SelectionChanged" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelName" FontWeight="Bold" ItemsSource="{Binding ViewModel.ResourcesToChooseFrom, RelativeSource={RelativeSource AncestorType=Window},UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Resource, Mode=TwoWay}" SelectedValuePath="Refno" DisplayMemberPath="Name" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> <DataGridTemplateColumn Header="Date Out" Width="20*" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Background="LightGray" FontWeight="Bold" Text="{Binding DateOut, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}, StringFormat={}{0:MMM-dd-yyyy hh:ss tt}}">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Toolkit:DateTimePicker Background="LightGray" FontWeight="Bold" Value="{Binding Path=DateOut, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}}" Format="Custom" FormatString="MMM dd yyyy hh:ss tt"></Toolkit:DateTimePicker>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Date In" Width="20*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Background="LightGray" FontWeight="Bold" Text="{Binding DateIn, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}, StringFormat={}{0:MMM-dd-yyyy hh:ss tt}}">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Toolkit:DateTimePicker Background="LightGray" FontWeight="Bold" Value="{Binding Path=DateIn, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}}" Format="Custom" FormatString="MMM dd yyyy hh:ss tt"></Toolkit:DateTimePicker>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Here is the code behind for the xaml (xaml.cs):
public JobEditorViewModel ViewModel
{
get { return viewModel; }
}
private void cmbPersonnelTypes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var combobox = sender as ComboBox;
if (combobox != null)
{
var selectedPersonnelType = combobox.SelectedItem as PersonnelType;
viewModel.SetResourcesToChooseFrom(selectedPersonnelType);
}
}
Here is the code in the viewModel:
public BindingList<PersonnelType> PersonnelTypes
{
get; set;
}
public JobEditorViewModel(int jobid, string region, DataAccessDataContext db, ServiceUserControlViewModel serviceViewModel)
{
PersonnelTypes = new BindingList<PersonnelType>(_db.PersonnelTypes.OrderBy(p => p.Head).ThenBy(p => p.Description).ToList());
}
private BindingList<Resource> _resourcesToChooseFrom;
public BindingList<Resource> ResourcesToChooseFrom
{
get { return _resourcesToChooseFrom; }
set
{
_resourcesToChooseFrom = value;
NotifyPropertyChanged("ResourcesToChooseFrom");
}
}
public void SetResourcesToChooseFrom(PersonnelType personnelType)
{
ResourcesToChooseFrom =
new BindingList<Resource>(_db.Resources.Where(r => r.Head == personnelType.Head && r.Refno > 2).OrderBy(r=>r.Name).ToList());
}
If you need to see more, let me know
Well, with some help from a colleague here at work, we figured out what I needed to do. Multibinding is the answer. First off we kind of hacked around so that the two combo boxes could be in the same column by placing them both in a grid and placing the grid in the one column. So now both combo boxes can see each other because they are in the same DataGridTemplateColumn. Before, we couldn't get them to see each other because they lost scope of each other in being two separate DataGridTemplateColumns.
Here's what we did in the xaml:
<DataGridTemplateColumn Header="Personnel Type-Name" Width="Auto" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding PersonnelType.Description}"/>
</Border>
<Border Grid.Column="1" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding Resource.Name}"/>
</Border>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox Name="cmbPersonnelTypes" Grid.Column="0" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" />
<ComboBox Name="cmbPersonnelName" Grid.Column="1" FontWeight="Bold" SelectedItem="{Binding Resource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Refno" DisplayMemberPath="Name" >
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource FilteredPersonnelConverter}">
<Binding Path="ViewModel.AvailablePersonnel" RelativeSource="{RelativeSource AncestorType=Window}"/>
<Binding Path="SelectedItem" ElementName="cmbPersonnelTypes"/>
<Binding Path="ViewModel.SelectedGlobalResourceViewOption" RelativeSource="{RelativeSource AncestorType=Window}"/>
</MultiBinding>
</ComboBox.ItemsSource>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
You'll notice there is a ValueConverter in the MultiBinding called FilteredPersonnelConverter. This value converter does all the filtering for me. Here's the code for that:
public class FilteredPersonnelListValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var allResources = values[0] as IList<Resource>;
var personnelType = values[1] as PersonnelType;
var selectedGlobalResourceView = values[2] as ResourceViewOption;
if (personnelType == null)
return allResources;
if(selectedGlobalResourceView.ResourceViewTitle=="Regional")
return allResources.Where(r => r.Head == personnelType.Head && r.Obsolete == false && r.Location.Region.RegionID.Trim()==SettingsManager.OpsMgrSettings.Region.Trim()).OrderBy(r => r.Name).ToList();
if (selectedGlobalResourceView.ResourceViewTitle == "Local")
return allResources.Where(r => r.Head == personnelType.Head && r.Obsolete == false && r.LocnID.Trim() == SettingsManager.OpsMgrSettings.LOCNCODE.Trim()).OrderBy(r => r.Name).ToList();
return allResources.Where(r => r.Head == personnelType.Head &&r.Obsolete==false).OrderBy(r => r.Name).ToList();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
So if anyone else is doing something like this, look into Multibinding, it will change your life
When the user changes the PersonelType dropdown in the view, the ViewModel should then filter the list of Resources (which should update the second dropdown box with the correct information).
Really your view model just needs to be set up to respond to changes on the view. That's what it boils down to.
Looking here:
public JobEditorViewModel(int jobid, string region, DataAccessDataContext db, ServiceUserControlViewModel serviceViewModel)
{
PersonnelTypes = new BindingList<PersonnelType>(_db.PersonnelTypes.OrderBy(p => p.Head).ThenBy(p => p.Description).ToList());
}
it looks like you set the personel types in the constructor, but you aren't responding to user changes. You need to do that in the PersonelType Binding in your view model.
EDIT
I see now that you're handling the selection changed. But really I think this should be done in the view model itself. Specifically because you actually access the viewmodel from the view to do make your changes.
Example
So here's my VM:
class ViewModel : INotifyPropertyChanged
{
DispatcherTimer timer = new DispatcherTimer();
public ViewModel()
{
// Create my observable collection
this.DateTimes = new ObservableCollection<DateTime>();
// Every second add anothe ritem
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
// This adds to the collection
this.DateTimes.Add(DateTime.Now);
}
public ObservableCollection<DateTime> DateTimes { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
}
and My View:
<ListBox ItemsSource="{Binding DateTimes}"/>
Notice that I don't rebuild the collection. I just set it once, and then change it's size as neccesary.