ListView DataTemplate binding - wpf

I have the following ListView:
<ListView Name="listView">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility"
Value="Collapsed"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<CheckBox
Margin="0"
VerticalAlignment="Center"
IsChecked="{Binding IsChecked}"
Visibility="{Binding IsChecked, Converter={StaticResource boolToVis}}">
</CheckBox>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="0"
Text="{Binding Text}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The items in the ListView are of the below type:
public class CheckBoxListViewItemSource : INotifyPropertyChanged
{
public CheckBoxListViewItemSource(String text)
{
m_text = text;
}
public bool IsChecked
{
get { return m_checked; }
set
{
if (m_checked == value) return;
m_checked = value;
RaisePropertyChanged("IsChecked");
}
}
public String Text
{
get { return m_text; }
set
{
if (m_text == value) return;
m_text = value;
RaisePropertyChanged("Text");
}
}
public override string ToString()
{
return Text;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
PropertyChangedEventHandler eh = PropertyChanged;
if (eh != null)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
private bool m_checked;
private String m_text;
}
The visibility of the checkbox in the ListView is bound to the value of IsChecked of the ListViewItem. The converter is a simple bool to visibility converter:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Boolean)
{
return ((bool)value) ? Visibility.Visible : Visibility.Collapsed;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
In the code behind of the ListView I have:
void listView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.RemovedItems)
{
CheckBoxListViewItemSource source = item as CheckBoxListViewItemSource;
source.IsChecked = false;
}
foreach (var item in e.AddedItems)
{
CheckBoxListViewItemSource source = item as CheckBoxListViewItemSource;
source.IsChecked = true;
}
}
The binding for the checkbox visibility is not working for me. The default IsChecked value is false so the list appears with no checkboxes. If I select an item, the checkbox does not appear.
However, if I set the default value of IsChecked to true, all of the list items appear with a checkbox and if I select an item and then deselect it, the checkbox correctly disappears.
What I am trying to achieve is that all of the items start off with no checkbox, selecting an item displays a checked checkbox, and deselecting an item hides the checkbox.
Any ideas where I am going wrong?

Manually set width of the first GridViewColumn to a fixed value. It seems ListView sets width of it to zero if it contains nothing and doesn't update width when checkboxes start to appear.
Alternatively, change code of BoolToVisibilityConverter to return Visibility.Hidden instead of Visibility.Collapsed.

I know this has been answered but I got around this problem using the following:
<GridViewColumn Header="MyColumn">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding MyItem, UpdateSourceTrigger=PropertyChanged}" ContentTemplate="{StaticResource myTemplate}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
And in the Window I had a DataTemplate defined for the type that MyItem was:
<Window.Resources>
<DataTemplate DataType="{x:Type myViewModels:MyItemViewModel}" x:Key="myTemplate" >
...template code
</DataTemplate>
</Window.Resources>

Related

WPF Column in ListView shall represent a ComboBox when IsFocused = true but a simple TextBox when IsFocused = false

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>

WPF, How to change the properties (visibility.. ) of dynamically created UI components on load

I want to change the properties of the dynamically populated UI components.
Here is my sample UI. The label and three textboxes created dynamically.
I want to change the visibility of the third textbox if the label content is R11.
and add a combo box with the static resource if the label is R12
Here is my sample code
my main screen XAML
<StackPanel>
<control:MethodControl></control:MethodControl>
<ContentControl Content="{Binding ChildViewModel}" />
</StackPanel>
MainViewModel
class MethodViewModel : ViewModelBase
{
#region Properties
private Method _method;
private PropertyViewModel _childViewModel;
#endregion
#region Getter & Setters
public PropertyViewModel ChildViewModel
{
get { return this._childViewModel; }
set
{
if (this._childViewModel != value)
{
this._childViewModel = value;
RaisePropertyChanged(() => ChildViewModel);
}
}
}
public Method Method
{
get { return _method; }
}
public ICommand UpdateCommand
{
get; private set;
}
#endregion
#region Constructor
/// <summary>
/// Initialize a new interface of the MEthodViewModel class
/// </summary>
public MethodViewModel()
{
//test
_method = new Method();
PropertyViewModel pwm = new PropertyViewModel();
pwm.CollectProperties(_method.Name, _method.Helper);
ChildViewModel = pwm;
UpdateCommand = new UpdateCommand(SaveChanges, () => string.IsNullOrEmpty(_method.Error));
}
#endregion
#region Functions
public void SaveChanges()
{
PropertyViewModel pwm = new PropertyViewModel();
pwm.CollectProperties(_method.Name, _method.Helper);
ChildViewModel = pwm;
}
Child View Model
class PropertyViewModel : ViewModelBase
{
private ObservableCollection<Property> _properties;
public ObservableCollection<Property> Properties
{
get { return _properties; }
}
public PropertyViewModel(string method, string reflection)
{
_properties = new ObservableCollection<Property>();
CollectProperties(method, reflection);
}
public void CollectProperties(string method, string reflection)
{
_properties.Clear();
int methodindex = Array.IndexOf((String[])Application.Current.Resources["MethodNames"], method);
switch (methodindex)
{
case 0:
foreach (String prop in (String[])Application.Current.Resources["Result1"])
{
PopulateProperty(prop, true);
}
break;
default:
foreach (String prop in (String[])Application.Current.Resources["Result2"])
{
PopulateProperty(prop, true);
}
break;
}
}
public PropertyViewModel()
{
_properties = new ObservableCollection<Property>();
}
private void PopulateProperty(string prop, bool p1)
{
Property temp = new Property(prop, "", 0, "");
_properties.Add(temp);
}
}
ChildViewModel XAML
<StackPanel >
<ItemsControl ItemsSource = "{Binding Properties}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<Label Content="{Binding Name}"></Label>
<TextBox Text = "{Binding Path, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding StdDev, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Unit, Mode=TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
and my resources
<x:Array x:Key="MethodNames" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>MM1</sys:String>
<sys:String>MM2</sys:String>
<sys:String>MM3</sys:String>
</x:Array>
<x:Array x:Key="HelperMethods" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>HM1</sys:String>
<sys:String>HM2</sys:String>
<sys:String>HM3</sys:String>
</x:Array>
<x:Array x:Key="Result1" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>R11</sys:String>
<sys:String>R12</sys:String>
<sys:String>R13</sys:String>
</x:Array>
<x:Array x:Key="Result2" Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>R21</sys:String>
<sys:String>R22</sys:String>
<sys:String>R23</sys:String>
</x:Array>
<DataTemplate DataType="{x:Type modelViews:PropertyViewModel}">
<control:PropertyControl />
</DataTemplate>
part of the UI is changing with the selections of the combo boxes.
I need whole 3 text boxes for some options, only 1 for some and 1 textbox and 1 combo box for some options depends on the label name.
How can I add this property to dynamically populated user control?
You could add one or several triggers to the DataTemplate, e.g.:
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<Label Content="{Binding Name}"></Label>
<TextBox Text = "{Binding Path, Mode=TwoWay}" Width = "100" Margin="3 5 3 5"/>
<TextBox Text="{Binding StdDev, Mode=TwoWay}" Width="100" Margin="3 5 3 5"/>
<TextBox x:Name="third" Text="{Binding Unit, Mode=TwoWay}" Width="100" Margin="3 5 3 5"/>
<ComboBox x:Name="combo" Visibility="Collapsed" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Name}" Value="R11">
<Setter TargetName="third" Property="Visibility" Value="Collapsed" />
<Setter TargetName="combo" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
I find an easy solution to my question, so let me add it.
I add visibility in the properties and bind the textbox visibility to it.
here is the bool the visibility converter
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
 
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the conversion from visibility to bool
}
}
Resource
<converter:BoolToVisibilityConverter x:Key="converter"></converter:BoolToVisibilityConverter>
text box
<TextBox Visibility="{Binding HasUnit,
Converter={StaticResource converter}}" />
 

WPF DataGridTemplateColumn MVVM DataContext Property Accessibility Issue

I am trying to get access to a Boolean property value inside each row so I can use it to set a button visibility, however I am having trouble accessing this with a DataGridTemplateColumn. I was able to get the entire row object into a parameter that I pass to the button command, however I can't get just the UseSetting value to pass to the Visibility converter. I tried piggy backing off the text column as shown below, however the converters only seem to fire when the view is first loaded. Using breakpoints I can see that subsequent changes to the UseSetting property do not fire the converters. I do have NotifyOfPropertyChange setup correctly on the custom class used in the DataGrid.
What is the best way to gain access to a row property when using DataGridTemplateColumn? The reason why I am creating my own check boxes inside a DataGridTemplateColumn instead of using a CheckboxColumn is because the CheckboxColumn requires the row to be selected before it can be checked, and I need my checkbox to check upon a single click.
To be clear, there is no code behind for this view. Everything is in the view model, like the data grid's item source which is an ObservableCollection of the custom class "SharedSetting" that I included below.
<DataGrid MaxHeight="400" VerticalScrollBarVisibility="Auto" BorderThickness="1" CanUserAddRows="False" CanUserDeleteRows="False" BorderBrush="{DynamicResource AccentBaseColorBrush}" GridLinesVisibility="Horizontal" AutoGenerateColumns="False" ItemsSource="{Binding SharedSettings, NotifyOnSourceUpdated=True}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="10" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Width="250" Header="Setting" Binding="{Binding Setting}" />
<DataGridTextColumn Width="300" Header="Value" ElementStyle="{StaticResource WrapText}" Binding="{Binding Value}" />
<DataGridTextColumn Width="75" Header="Use Setting" Binding="{Binding UseSetting, Mode=TwoWay}" x:Name="stackRowUseSetting" />
<DataGridTemplateColumn Width="50" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid Width="30" Height="30" x:Name="stackRow2">
<Button Background="Transparent" Foreground="{StaticResource AccentColorBrush}" ToolTip="Do Not Use Setting" Visibility="{Binding ElementName=stackRowUseSetting, Path=Binding, Converter={StaticResource TrueToVisibleConverter}}" BorderThickness="0" Margin="0,0,0,0" DataContext="{Binding ElementName=MainGrid, Path=DataContext}" Command="{Binding ToggleUseSettingCommand}" CommandParameter="{Binding ElementName=stackRow2,Path=DataContext}">
<iconPacks:PackIconMaterial Kind="CheckCircleOutline" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
</Button>
<Button Background="Transparent" Foreground="{StaticResource AccentColorBrush}" ToolTip="Use Setting" Visibility="{Binding ElementName=stackRowUseSetting, Path=Binding, Converter={StaticResource FalseToVisibleConverter}}" BorderThickness="0" Margin="0,0,0,0" DataContext="{Binding ElementName=MainGrid, Path=DataContext}" Command="{Binding ToggleUseSettingCommand}" CommandParameter="{Binding ElementName=stackRow2,Path=DataContext}">
<iconPacks:PackIconMaterial Kind="CheckboxBlankCircleOutline" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
</Button>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Also, the above XAML is just what I have right now. There are most likely some items here that are redundant and not needed. I have added and removed so many things trying to get this to work that it's a bit sloppy at the moment.
Here is the SharedSetting class with INotifyPropertyChanged
public class SharedSetting : INotifyPropertyChanged
{
private bool _useSetting;
private object _o;
private string _value;
private string _setting;
private string _group;
public SharedSetting(string groupName, string settingName, string settingValue, object value, bool use=false)
{
Group = groupName;
Setting = settingName;
Value = settingValue;
Object = value;
UseSetting = use;
}
public SharedSetting()
{
}
public string Group
{
get { return _group; }
set
{
_group = value;
NotifyPropertyChanged();
}
}
public string Setting
{
get { return _setting; }
set
{
_setting = value;
NotifyPropertyChanged();
}
}
public string Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged();
}
}
public object Object
{
get { return _o; }
set
{
_o = value;
NotifyPropertyChanged();
}
}
public bool UseSetting
{
get { return _useSetting; }
set
{
_useSetting = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is one of the converters.
public sealed class TrueToVisibleConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var flag = false;
if (value is bool)
{
flag = (bool) value;
}
var visibility = (object) (Visibility) (flag ? 0 : 2);
return visibility;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Visibility)
{
var visibility = (object) ((Visibility) value == Visibility.Visible);
return visibility;
}
return (object) false;
}
}
UPDATE 3/14/18
To address the first answer supplied below regarding removing my DataContext setting and using the properties just like all of the other columns, that does not work. That was the first thing I tried long long ago only to learn that DataGridTemplateColumn doesn't inherit the row's data context like the other columns do (the reason for my frustration in my below comment yesterday). I've included a screenshot showing the intellisense error stating that the property doesn't exist, when it is used the same way as the column above it.
You overright DataContext for your Button. DataContext="{Binding ElementName=MainGrid, Path=DataContext}" is wrong, so delete it and bind to the property as you do it in DataGridTextColumn. And for the binding of Command to the command which is not in SharedSetting use ElementName(as you have done it for DataContext) or RelativeSource.
Update:
Should work, but alternatively you can try
<Button Visibility="{Binding DataContext.UseSetting, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}, Converter={StaticResource TrueToVisibleConverter}}" />

WPF Bind UserControl Property inside of the DataTemplate of a GridViewColumn.CellTemplate

I'm trying to figure out how to bind a property of a custom user control that is placed inside of the cell template of a list view control but it's not working. All of the DisplayMemberBinding fields are working as expected, and I'm getting the correct values, but inside of that custom control, nothing is updating.
WPF LIstView Control
<ListView Margin="10" x:Name="lvHistory">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Database" Width="150" DisplayMemberBinding="{Binding ActiveBackup.Database.Name, Mode=TwoWay}" />
<GridViewColumn Header="Start Time" Width="130" DisplayMemberBinding="{Binding ActiveBackup.StartTime, Mode=TwoWay}" />
<GridViewColumn Header="Time Elapsed" Width="100" DisplayMemberBinding="{Binding ActiveBackup.TimeElapsed, Mode=TwoWay}" />
<GridViewColumn Header="P2" Width="100" DisplayMemberBinding="{Binding Progress, Mode=TwoWay}" />
<GridViewColumn x:Name="progressColumn" Header="Progress" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<local:cProgressBarSmall x:Name="pr1" Value="{Binding Progress, Mode=TwoWay}" Visibility="Visible" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code-Behind in the cProgressBarSmall control.
public partial class cProgressBarSmall : UserControl
{
public ActiveBackup ActiveBackup { get; set; }
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(decimal), typeof(cProgressBarSmall));
private decimal _value;
public decimal Value
{
get
{
return (decimal) GetValue(ValueProperty);
}
set
{
_value = value;
SetValue(ValueProperty, value);
p1.Value = value.ToDoubleNotNull();
pLabel.Text = value.ToPercent(0);
if (value == 0)
{
p1.Visibility = Visibility.Hidden;
pLabel.Visibility = Visibility.Hidden;
}
else if (value.ToDoubleNotNull() >= p1.Maximum)
{
pLabel.Text = "Finished!";
pLabel.Foreground = new SolidColorBrush(Colors.Black);
}
}
}
}
}
I can't find a way to access the "pr1" because it's in a DataTemplate and therefore not directly accessible from the code-behind. Does binding not work through? The column before it (the "P2" column) is just at test column I put in just to make sure that the value is in fact updating and it is and that displays correctly, however the "progressColumn" always just shows the default value.
Is there anything special to data binding inside of a ListView.View > GridView > GridViewColumn > GridViewColumn.CellTemplate > DataTemplate hierarchy?
First, if you put a breakpoint in your setter you'll find that it's not hit by the binding. That's because the Binding is setting the dependency property, not the C# property. They're different. The C# property with get/set is an optional wrapper around the dependency property.
The correct way to do this is to have little or no code behind (code behind's not evil; you just don't need any for this one), but use a binding in the usercontrol xaml to update the UI. You can hide and show controls, and update label text, with style triggers in the usercontrol XAML. You don't need any code behind for this.
But here's the simplest way to adapt your existing code to something that works.
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(decimal), typeof(cProgressBarSmall),
new PropertyMetadata(0m, Value_ChangedCallback));
// Has to be static
private static void Value_ChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((cProgressBarSmall)d).OnValueChanged();
}
private void OnValueChanged()
{
p1.Value = Value.ToDoubleNotNull();
pLabel.Text = Value.ToPercent(0);
if (Value == 0)
{
p1.Visibility = Visibility.Hidden;
pLabel.Visibility = Visibility.Hidden;
}
else if (Value.ToDoubleNotNull() >= p1.Maximum)
{
pLabel.Text = "Finished!";
pLabel.Foreground = new SolidColorBrush(Colors.Black);
}
}

How to filter ListBox item ?

I define this simple class
public class SimpleClass
{
public string Val1 {get;set;};
public string Val2 {get;set;}
public string Res
{
get
{
string.Format("{0}_{1}", Val1, Val2 );
}
}
public SimpleClass(string v1, string v2)
{
Val1 = v1;
Val2 = v2;
}
public SimpleClass(string v1, int i)
{
if(i == 0)
{
Val1 = v1;
val2 = "";
}
if(i == 0)
{
Val2 = v1;
val1 = "";
}
}
}
Now, i define in the code this
List< SimpleClass > myList = new List<SimpleClass>();
myList.Add(new SimpleClass("a1", "b1");
myList.Add(new SimpleClass("a2", "b2");
myList.Add(new SimpleClass("a3", "b3");
myList.Add(new SimpleClass("a4", "b4");
And i define in the xaml 2 ListBox -
First that show all the a1...a4 items
Second that show all the b1...b4 items
Each item in the ListBox is CheckBox - and the content is the string of the item.
Now i want to define filter that will show in some other list only the SimpleClass.Res that was checked in the listBox.
==> that mean that if in the listBox that items that was checked are b1 and a3 that the only text in the third listbox will contain
a1_b1
a3_b3
How can i do it ?
I trying to use the CollectionViewSource but i can't define multi filter in this cases.
In UI you will have to have the checkbox bound to something so that you are aware of the status of the checked items from both ListBoxes.
Hence we bind it TwoWay to the ancestor ListBoxItem's IsSelected property. This way when checkboxes are checked they automatically register the checked item under ListBox.SelectedItems property.
Based on this we perform multi binding ListBox3.ItemsSource to ListBox1.SelectedItems and ListBox2.SelectedItems. The multi value converter called MergeSelectedItemsHelper simply performs a union of the two selected items lists.
I have used Array of TextBlock instead of List of SimpleClass to bind to all those ListBoxes.
XAML:
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<x:ArrayExtension x:Key="MyArraySource" Type="{x:Type TextBlock}">
<TextBlock Text="A" Tag="A1" DataContext="A_A1"/>
<TextBlock Text="B" Tag="B1" DataContext="B_B1"/>
<TextBlock Text="C" Tag="C1" DataContext="C_C1"/>
<TextBlock Text="D" Tag="D1" DataContext="D_D1"/>
</x:ArrayExtension>
<local:MergeSelectedItemsHelper
x:Key="MergeSelectedItemsHelper"/>
<DataTemplate x:Key="ListBox1ItemTemplate">
<CheckBox
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource
AncestorType={x:Type ListBoxItem}},
Mode=TwoWay}"
Content="{Binding Text}"/>
</DataTemplate>
<DataTemplate x:Key="ListBox2ItemTemplate">
<CheckBox
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource
AncestorType={x:Type ListBoxItem}},
Mode=TwoWay}"
Content="{Binding Tag}"/>
</DataTemplate>
</StackPanel.Resources>
<ListBox x:Name="ListBox1"
SelectionMode="Extended"
Margin="10"
ItemsSource="{StaticResource MyArraySource}"
ItemTemplate="{StaticResource ListBox1ItemTemplate}"
SelectionChanged="ListBox1_SelectionChanged">
</ListBox>
<ListBox x:Name="ListBox2"
SelectionMode="Extended"
Margin="10"
ItemsSource="{StaticResource MyArraySource}"
ItemTemplate="{StaticResource ListBox2ItemTemplate}"
SelectionChanged="ListBox1_SelectionChanged">
</ListBox>
<ListBox x:Name="ListBox3" Margin="10" DisplayMemberPath="DataContext">
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource MergeSelectedItemsHelper}">
<Binding Path="SelectedItems" ElementName="ListBox1"/>
<Binding Path="SelectedItems" ElementName="ListBox2"/>
</MultiBinding>
</ListBox.ItemsSource>
</ListBox>
</StackPanel>
</StackPanel>
Code Behind:
Now as ListBox.SelectedItems property is simply a non dependency property and also non-observable so the bindings will not automatically refresh. In code behind we will hage to refresh the binding when seletion is changed on the ListBox1 and ListBox2.
private void ListBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var bndexp = BindingOperations.GetMultiBindingExpression(
ListBox3, ItemsControl.ItemsSourceProperty);
if (bndexp != null)
{
bndexp.UpdateTarget();
}
}
As said earlier, the multi value converter simply joins the selected items together and gets called when multi-binding is refreshed...
public class MergeSelectedItemsHelper : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(
object[] values,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
var list1 = values[0] as IList;
var list2 = values[1] as IList;
var validList2 = list2 ?? new List<object>();
return list1 != null
? list1.Cast<object>().Union(validList2.Cast<object>())
: validList2.Cast<object>();
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Hope this helps.
Put a boolean public property in SimpleClass and bind it to the checkbox. Then in the second list bind visibility to the property.
<DataTrigger Binding="{Binding Path=DispDetail, Mode=OneWay}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger
If your checked items are in your ViewModel, you can simply filter the collection view source using a delegate
ICollectionView view = CollectionViewSource.GetDefaultView(this.myList);
view.Filter = obj =>
{
var sc = obj as SimpleClass;
// Do your checks to see if this object is valid, and return a bool
// For example,
return SelectedOptions.Contains(sc.Val1) ||
SelectedOptions.Contains(sc.Val2);
};
return view;

Resources