How to dynamiclly bind to a property path of an Object? - c#

i have the following ItemsControl defined, i want to bind the Content of a Button to a object Property path, which will be provided as a string through a method. The Items are generic. How can i achieve this ? because i can't set DisplayMemberPath and bind it to the ItemTemplate. (i cant't set both DisplayMemberPath and ItemTemplate)
<ScrollViewer Grid.Row="2"
Grid.Column="1"
Margin="5 3"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
Style="{DynamicResource MaterialDesignFlatButton}"
Margin="2"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock Text="{Binding}" />
</Button.Content>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
ViewModel:
public class SelectionViewModel<T> : DialogViewModelBase, IDialogViewModel
{
// Fields
private string _title;
private bool _showTitleSeparator;
private string _displayMemberPath;
private IEnumerable<T> _items;
// Properties
public string Title
{
get { return _title; }
set { _title = value; Notify(); }
}
public IEnumerable<T> Items
{
get { return _items; }
set { _items = value; Notify(); }
}
// Member Path to bind to
public string DisplayMemberPath
{
get { return _displayMemberPath; }
set { _displayMemberPath = value; Notify(); }
}
}
if i remove the ItemTemplate and simply bind the DisplayMemberPath of the ItemsControl to the Property in my ViewModel it works fine.

I was able to do this using a converter and reflection.
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.Contains(DependencyProperty.UnsetValue))
{ return null; }
object source = values[0];
string path = (string)values[1];
Type sourceType = source.GetType();
System.Reflection.PropertyInfo propertyInfo = sourceType.GetProperty(path);
return propertyInfo.GetValue(source, null);
}

Binding has to be set with Path to the property name of the class whose list is bound to the ItemsSource.
if your class looks like:
Class MyItem{
String Prop1 { get; }
}
Then binding will be
<TextBlock Text="{Binding Path=Prop1}" />

Related

C# WPF How can I edit the values in a collection presented in a ListView / Textbox

I have a list view that displays the EditCollection property stored in a viewmodel which is of type ObservableCollection. It displays each element as a textbox with the string as its text so I can edit the string of each element. There is an Add button that adds an element whose handler is AddToCollection. There is also a Save button. When it is clicked, EditCollection should be copied into another ObservableCollection property called Collection. But what actually happens is that any added elements are shown, but their edited values do not appear in EditCollection, only their default values.
private ObservableCollection<string> _editCollection;
public ObservableCollection<string> EditCollection
{
get { return _editCollection; }
set
{
_editCollection = value;
OnPropertyChanged("EditCollection");
}
}
private ObservableCollection<string> _collection;
public ObservableCollection<string> Collection
{
get { return _collection; }
set
{
_collection = value;
_editCollection = new ObservableCollection<string>(_collection);
OnPropertyChanged("Collection");
OnPropertyChanged("EditCollection");
}
}
public void Save(object item)
{
string value;
if (EditCollection.Count > 1)
{
value = EditCollection[1];
}
; // break point: value = "default value" even if I edit the textbox
Collection = new ObservableCollection<string>(new List<string>( EditCollection ));
}
public void AddToCollection(object item)
{
EditCollection.Add("default value");
OnPropertyChanged("EditCollection");
}
view.xaml
<Button Content="Save" Width="50" HorizontalAlignment="Right"
Command="{Binding SaveCommand}"/>
<ListView Grid.Row="1" ItemsSource="{Binding Path=EditCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=.,
UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="2" Content="Add" Width="50" HorizontalAlignment="Center"
Command="{Binding AddToCollectionCommand}"/>
The way to do this is bind to a class instead of the string directly.
private ObservableCollection<Item> _editCollection;
public ObservableCollection<Item> EditCollection
{
get { return _editCollection; }
set
{
_editCollection = value;
OnPropertyChanged("EditCollection");
}
}
public class Item : INotifyPropertyChanged
{
private string _text;
public string Text
{ get { return _text; }
set{ _text = value; OnPropertyChanged("Text");
}
}
<ListView Grid.Row="1" ItemsSource="{Binding Path=EditCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=Text,
UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Get Listbox SelectedValue in MVVM

I have a MainWindow that contains A listbox, and a ContentControl, each time you select something from the Listbox, the ContentControl will display something else.
<ContentControl Content="{Binding ElementName=SomeList, Path=SelectedItem.Content}" />
<ListBox x:Name="SomeList" Margin="0 16 0 16" SelectedIndex="0" SelectedValue="{Binding X}"
ItemsSource="{Binding DemoItems}">
ViewModel:
private string _X;
public string X
{
get { return _X; }
set
{
_X = value;
NotifyOfPropertyChange("X");
}
}
Trying to display X will result in the same thing:
namespace.DemoItem
DemoItem.cs:
public class DemoItem : INotifyPropertyChanged
{
private object _icon;
private string _name;
private object _content;
private Thickness _marginRequirement;
public DemoItem(object icon, string name, object content, Thickness margin, IEnumerable<DocumentationLink> documentation)
{
_icon = icon;
_name = name;
Content = content;
_marginRequirement = margin;
Documentation = documentation;
}
}
So how is it possible to only get the name?
Data binding only works with public properties. So add a Name property
public class DemoItem : INotifyPropertyChanged
{
...
public string Name { get { return _name; } }
}
Assuming that yous intention is to selected a DemoItem by its Name, you should also set the ListBox's SelectedValuePath in conjunction with SelectedValue:
<ListBox ItemsSource="{Binding DemoItems}"
SelectedValuePath="Name"
SelectedValue="{Binding X}" />
Then simply display the selected item's Name like this:
<TextBlock Text="{Binding X}" />

NullToVisibilityConverter not being triggered by selection change

I have a StackPanel that I would like to hide when there isn't a selected item from a ListView in the same window. Currently, when I open the window, there is no selected item and the StackPanel is hidden, but when I do select something from the ListView, no change occurs.
I am binding the SelectedItem in the ListView like:
<ListView
MinHeight="0"
MaxHeight="500"
Margin="10,10,10,0"
Background="#e7f5f4"
BorderThickness="0"
ItemsSource="{Binding Issues}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding SelectedIssue}"
SelectionMode="Single">
Where "SelectedIssue" is a custom class property in my ViewModel (my entire window has the same DataContext). I am currently binding the Visibility property of my StackPanel as:
<StackPanel
Grid.Column="1"
Margin="13,0,0,5"
VerticalAlignment="Bottom"
Background="#ebf7f6"
Orientation="Horizontal"
Visibility="{Binding SelectedIssue,
Converter={StaticResource NullToVisibilityConverter},
UpdateSourceTrigger=PropertyChanged}">
And my converter is:
public class NullToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
What am I missing?
EDIT: Here is my getter/setter
private Issue _selectedIssue;
public Issue SelectedIssue
{
get { return _selectedIssue; }
set { Set(ref _selectedIssue, value); }
}
public void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public bool Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
apparently the problem is that you have not implemented INotifyPropertyChanged or you not raise (in the SelectedIssue property setter) the PropertyChanged Event.
but you can do simpler, binding the StackPanel directly to ListView.SelectedItem:
<ListView x:Name="listView"
MinHeight="0"
MaxHeight="500"
Margin="10,10,10,0"
Background="#e7f5f4"
BorderThickness="0"
ItemsSource="{Binding Issues}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding SelectedIssue}"
SelectionMode="Single">
<StackPanel
Grid.Column="1"
Margin="13,0,0,5"
VerticalAlignment="Bottom"
Background="#ebf7f6"
Orientation="Horizontal"
Visibility="{Binding SelectedItem, ElementName=listView
Converter={StaticResource NullToVisibilityConverter}" >

Specify Converter inside DataTemplate in UserControl

I have created a user control that has a Label and a ComboBox. It is used like this:
<cc:LabeledComboBox
HeaderLabelContent="Months"
ItemsSource="{Binding AllMonths}"
SelectedValue="{Binding SelectedMonth}"/>
And here is what the UserControl XAML looks like:
<UserControl x:Class="CustomControls.LabeledComboBox" ...>
<UserControl.Resources>
<converters:MonthEnumToTextConverter x:Key="MonthEnumToTextConverter" />
</UserControl.Resources>
<DockPanel>
<Label x:Name="LblValue" DockPanel.Dock="Top"/>
<ComboBox x:Name="LstItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<!-- TODO: Fix so that the Converter can be set from outside -->
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</UserControl>
In the comment above you can see my problem. The control is generic (the ComboBox can contain pretty much anything) but on the Binding inside the DataTemplate I have specified a Converter that is very specific.
How can I specify the Converter from outside the UserControl?
I'm hoping for some kind of solution using a dependency property like this:
<cc:LabeledComboBox
...
ItemConverter="{StaticResource MonthEnumToTextConverter}"/>
You may have an internal binding converter that delegates its Convert and ConvertBack calls to one set is settable as dependency property:
<UserControl ...>
<UserControl.Resources>
<local:InternalItemConverter x:Key="InternalItemConverter"/>
</UserControl.Resources>
<DockPanel>
...
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding
Converter={StaticResource InternalItemConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</UserControl>
The internal converter could look like this:
class InternalItemConverter : IValueConverter
{
public LabeledComboBox LabeledComboBox { get; set; }
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (LabeledComboBox != null && LabeledComboBox.ItemConverter != null)
{
value = LabeledComboBox.ItemConverter.Convert(
value, targetType, parameter, culture);
}
return value;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (LabeledComboBox != null && LabeledComboBox.ItemConverter != null)
{
value = LabeledComboBox.ItemConverter.ConvertBack(
value, targetType, parameter, culture);
}
return value;
}
}
And finally the dependency property code like this:
public partial class LabeledComboBox : UserControl
{
private static readonly DependencyProperty ItemConverterProperty =
DependencyProperty.Register(
"ItemConverter", typeof(IValueConverter), typeof(LabeledComboBox));
public IValueConverter ItemConverter
{
get { return (IValueConverter)GetValue(ItemConverterProperty); }
set { SetValue(ItemConverterProperty, value); }
}
public LabeledComboBox()
{
InitializeComponent();
var converter = (InternalItemConverter)Resources["InternalItemConverter"];
converter.LabeledComboBox = this;
}
}
You can create multiple datatemplates for the the combobox items and then you can control what and how you want to display your comboxitem like below
<DataTemplate DataType="{x:Type vm:MonthDataTypeViewModel}" >
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
<DataTemplate DataType={x:Type vm:OtherViewModel}">
<TextBlock Text="{Binding}"/>
</DataTemplate>
If you do not have multiple viewmodels then you can use a template selector to select different data templates based on some property in your viewmodel.
OP here. Presenting the solution that I'll use until I find something better.
I don't specify only the Converter, but the whole DataTemplate:
<cc:LabeledComboBox>
<cc:LabeledComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
</cc:LabeledComboBox.ItemTemplate>
</cc:LabeledComboBox>
And here's the ItemTemplate dependency property:
public partial class LabeledComboBox : UserControl
{
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
"ItemTemplate",
typeof(DataTemplate),
typeof(LabeledComboBox),
new PropertyMetadata(default(DataTemplate), ItemTemplateChanged));
private static void ItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var labeledComboBox = (LabeledComboBox)d;
labeledComboBox.LstItems.ItemTemplate = (DataTemplate)e.NewValue;
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
// ...
}
EDIT: reworked to not use my personal example to avoid confusion ...
On your user control code behind you could define your dependency property.
I don't know what type your converters derive from so change 'myConverterType' to the type of converters you use.
public bool ItemConverter
{
get { return (myConverterType)GetValue(IntemConverterProperty); }
set { SetValue(ItemConverterProperty, value); }
}
public static readonly DependencyProperty ItemConverterProperty =
DependencyProperty.Register("ItemConverter", typeof(myConverterType),
typeof(LabeledComboBox), null);
In XAML you should then just be able to set the converter property as per your example. In my example it is used like this:
<cc:LabeledComboBox ItemConverter="{StaticResource theSpecificConverter}"/>
Then use this property, on your user control xaml, like this:
<ComboBox x:Name="LstItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={Binding ItemConverter, ElementName=UserControl}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Checkbox WPF MVVM

Hi all i have problem in this code, please help me..
I have view
<StackPanel Orientation="Horizontal" Margin="3">
<Label Content="Audit Type" MinWidth="100"/>
<Label Content=":"/>
<StackPanel Orientation="Vertical">
<ListBox ItemsSource="{Binding Items}" Margin="3" SelectionMode="Extended" MinWidth="180">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="check" Content="{Binding Value}" IsChecked="{Binding IsChecked, Mode=TwoWay}" Margin="3" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
and for View model
private List<AuditTypeExport> _items;
private List<string> _value;
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public List<AuditTypeExport> Items
{
get { return _items; }
}
public List<string> Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
And ViewModel Constractor
_items = _model.GetAuditType();
_value = _model.GetAuditType().Select(item => item.Name).ToList();
For your information
public class AuditTypeExport
{
private int _id;
private string _name;
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
The result : checkbox appeares, but the content doesn't and I don't have a clue why.
Question Number 2 : I want to get the value back, how can I do that?
Thank you
It is unclear how you are using your ViewModel. Is that bound to the form? Or each item in the ListBox?
It looks like your ListBox is bound to the Items collection of your VM, so the ItemTemplate will be used with a AuditTypeExport as the data context. You are binding to "Value" and "IsChecked" properties which do not exist on the AuditTypeExport class.
What you are trying to do here is bind a property of type List<String> Value to CheckBox's Content property which is of type Object.
To simplify, you are assigning a collection of strings to a string. Which is not a good thing. And that is why it does not work.
Try using ItemsControl to show Value property or use an IValueConverter to convert List<String> to comma separated string.

Categories