WPF: How to get SelectedItem after applying value converter? - wpf

I have control where I bind the images. The code in my view model looks as following:
public List<IDocument> SelectedEventPhotoList
{
get { return _selectedEventPhotoList; }
set
{
if (Equals(value, _selectedEventPhotoList))
return;
_selectedEventPhotoList = value;
RaisePropertyChanged(() => SelectedEventPhotoList);
}
}
public IDocument SelectedEventPhoto
{
get { return _selectedEventPhoto; }
set
{
if (Equals(value, _selectedEventPhoto))
return;
_selectedEventPhoto = value;
RaisePropertyChanged(() => SelectedEventPhoto);
}
}
The binding looks as following:
<ListView Grid.Row="0"
ItemsSource="{Binding SelectedEventPhotoList, Converter={StaticResource PathToFileConverter}}"
SelectedItem="{Binding SelectedEventPhoto}"
As you can see I have a list of IDocument types to bind to ItemsSource and SelectedItem is of IDocument type. But, images have Source property that is of type string and I've used PathToFileConverter value converter to convert IDocument types to strings.
The issue is in fact that after using converter, SelectedItem is null.
How can I achieve the SelectedItem keeps IDocument type, which is not null?

You should apply the converter to the Source property binding of the Image and not to the ItemsSource of the ListView
The type of the property that is bound to the SelectedItem property of a ListView should always be T if the ItemsSource property is bound or set to an IEnumerable<T>.

You should remove the converter from the ItemSource binding, and add DisplayMemberPath="PathOfFileProperty", which 'PathOfFileProperty' is the string property on IDocument which indicate the file path

Related

Listing and binding to properties of a type, not a collection of types

I have a Listbox with property names of type Color from a MySettings object. I want to create another control (let's say a TextBox, the type is irrelevant) that binds to the value of property name listed.
I am at a loss as to how to achieve that. As it is now, the listbox is just a list of strings, obviously, it has no knowledge of the MySettings object.
One of the things I thought about is creating a collection of objects of a custom type that somehow binds to the MySettings object, and setting that as ItemsSource for the ListBox.
I know I trying to reinvent the wheel here, so I would appreciate any pointers to get me started.
Code-behind, ListBox is currently defined in xaml with no properties other than x:Name="listBox"
foreach (var prop in settings.GetType().GetProperties()
.Where(p => p.PropertyType == typeof(System.Drawing.Color))
.OrderBy(p => p.Name))
{
listBox.Items.Add(new ListBoxItem()
{
Content = prop.Name,
});
}
MySettings contains several properties:
[UserScopedSetting()]
[ConfigurationProperty(nameof(BackColor), DefaultValue = "GhostWhite")]
public System.Drawing.Color BackColor
{
get { return (System.Drawing.Color)base[nameof(BackColor)]; }
set
{
base[nameof(BackColor)] = value;
OnPropertyChanged();
}
}
[UserScopedSetting()]
[ConfigurationProperty(nameof(ForeColor), DefaultValue = "Black")]
public System.Drawing.Color ForeColor
{
get { return (System.Drawing.Color)base[nameof(ForeColor)]; }
set
{
base[nameof(ForeColor)] = value;
OnPropertyChanged();
}
}
You could assign the ListBox's ItemsSource to a collection of name/value pairs (an anonymous class here, but could as well be a regular class):
listBox.ItemsSource = settings.Properties
.OfType<SettingsProperty>()
.Where(p => p.PropertyType == typeof(System.Drawing.Color))
.Select(p => new
{
Name = p.Name,
Value = settings[p.Name]
});
Then declare a ListBox ItemTemplate that shows the Name property, and a TextBlock that binds to the Value property of the selected ListBox item:
<ListBox x:Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="{Binding ElementName=listBox, Path=SelectedItem.Value}"/>

ComboBox SelectedValue does not update when the property used by SelectedValuePath is updated

My goal is to have the bound property value update with the correct value if the selected profile changes its Name. Right now it maintains the old value which breaks binding on serialization/deserialization.
My XAML;
<ComboBox SelectedValue="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="120" SelectedValuePath="Name" ItemsSource="{Binding Profiles}, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Profile}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox
My collection of selectible profiles;
ObservableCollection<Profile> profiles = new ObservableCollection<Profile>();
my Profile class;
public partial class Profile: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return _name; }
set { if (value != _name) { _name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); } }
}
private string _name = "[Default]";
}
INotify seems to be working just fine because the displayed Name in the combobox does update for any profiles listed when their name changes. But when serialized the prop bound to SelectedValue known as value does not contain the new value for Name.
You are correct. Looks like SelectedValue is only updated when the selection changes in the ComboBox and changing the Name doesn't actually change the current selection thus no update. You could instead bind the SelectedItem to a property. That would contain the updated Name. Any controls would bind to SelectedItem.Name instead of Value.
If you must use SelectedValue then you could unselect and reselect the comboBox's selection (yuck!) or force the SelectedValue to update whenever you make changes to the Name property, for example:
if (comboBox1.SelectedItem != null) {
if (!comboBox1.SelectedValue.Equals(((Profile) comboBox1.SelectedItem).Name)) {
comboBox1.SelectedValue = ((Profile) comboBox1.SelectedItem).Name;
}
}
again, not that pretty but it works. There may be a way to get the SelectedValue's BindingExpression and force the update but simply calling UpdateSource on it doesn't seem to do anything unless the selection has changed.
comboBox1.GetBindingExpression (ComboBox.SelectedValueProperty).UpdateSource ();

WPF binding - DataGrid.Items.Count

in my View, there are a DataGrid and a TextBox, which is bound to the DataGrid's Items.Count property:
<DataGrid x:Name="dataGrid" ItemsSource="{Binding dataTable}"/>
<TextBox Text="{Binding Items.Count,ElementName=dataGrid,Mode=OneWay,StringFormat={}{0:#}}"/>
The ViewModel has a property (e.g. ItemsCount) which I'd like to be bound to the Items.Count property of the DataGrid, but have no idea, how to achieve this.
class ViewModel : INotifyPropertyChanged
{
public DataTable dataTable {get;set;}
public int ItemsCount {get;set;}
}
Maybe I could also use the Rows.Count property of the DataTable the DataGrid is bound to, but how would i bind or link the two properties in the ViewModel?
So I basically want the ItemsCount property to be synchronized with the dataTable.Rows.Count property.
A common way to achieve your requirements are to declare properties to data bind to the UI controls:
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Items}" />
<TextBox Text="{Binding ItemsCount}" />
...
// You need to implement the INotifyPropertyChanged interface properly here
private ObservableCollection<YourDataType> items = new ObservableCollection<YourDataType>();
public ObservableCollection<YourDataType> Items
{
get { return items; }
set { items = value; NotifyPropertyChanged("Items"); NotifyPropertyChanged("ItemCount"); }
}
public string ItemCount
{
get { Items.Count.ToString("{0:#}"); }
}
UPDATE >>>
As #Sivasubramanian has added his own requirement to your question, in case you need to update the item count specifically by adding to your collection, you can manually call the NotifyPropertyChanged method:
Items.Add(new YourDataType());
NotifyPropertyChanged("ItemCount");

Show Treeview only on item select from combobox

I have a WPF application/MVVM Pattern and it has a combobox and a treeview control.
What I wanted to do is to show the treeview control only when i select an item from the combobox.
For example : I have a Property called SelectedTransactionName
private string _selectedTransactionWsName;
public string SelectedTransactionName
{
set
{
if (_selectedTransactionWsName == value) return;
this._selectedTransactionWsName = value;
InitializaMessageElement();
}
get
{
return this._selectedTransactionWsName;
}
}
my InitializaMessageElement method will show matching transaction name to the selected item. But now I don't want to show the treeview on page load only when i do a select on the combobox.
On page load I want my window to show only the combobox.
Thanks
Your view model can contain a calculated boolean property to which your TreeView binds its Visibility property, e.g:
public bool IsTransactionNameSelected
{
get
{
return !string.IsNullOrEmpty(_selectedTransactionWsName);
}
}
You could then notify property change in the setter of the SelectedTransactionName:
set
{
if (_selectedTransactionWsName == value) return;
this._selectedTransactionWsName = value;
InitializaMessageElement();
this.NotifyOfPropertyChanged(() => this.IsTransactionNameSelected);
}
Then you can bind your TreeView Visibility property using the supplied BooleanToVisibilityConverter:
<TreeView
Visibility="{Binding IsTransactionNameSelected,
Converter={StaticResource BooleanToVisibilityConverter}" ...

DataGrid SelectedItem Being Bound to Wrong DataContext MVVM Pattern

I am trying to get the currently selected item of a datagrid that I have bound to a CollectionViewSource. However, it appears as if the SelectedItem property is not correctly binding to the property I have set in my ViewModel.
<Grid DataContext="{Binding CollectionView}">
<DataGrid ItemsSource="{Binding}" Margin="0,30,0,0" SelectedItem="{Binding SelectedRow}" />
</Grid>
When running the project, I see this error message in the output box of VS2010.
System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedRow' property not found on 'object' ''BindingListCollectionView' (HashCode=56718381)'. BindingExpression:Path=SelectedRow; DataItem='BindingListCollectionView' (HashCode=56718381); target element is 'DataGrid' (Name=''); target property is 'SelectedItem' (type 'Object')
I understand that the SelectedItem property of the datagrid is trying to bind to the CollectionViewSource, but I am not quite sure how to tell the SelectedItem to bind to the SelectedRow property of my ViewModel. Any help would be appreciated. Also, if you need any more information on my setup, please feel free to ask.
Here is the property in my ViewModel, just in case it is needed:
public DataRow SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
Change DataRow to whatever the actual type of object you are binding too is called.
public **Object each row represents in view model** SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
I did some more digging, and was able to come up with a solution. Essentially, I needed to tell the SelectedItem property to look back at the DataContext of the MainWindow.
I changed the XAML to:
<Grid DataContext="{Binding CollectionView}">
<DataGrid ItemsSource="{Binding}" Margin="0,30,0,0" SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.SelectedRow}">
</DataGrid>
</Grid>
and then change the property within my ViewModel to a DataRowView instead of DataRow
public DataRowView SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
Thanks everyone!
You have SelectedItem in your binding, and the name of your property is SelectedRow - make sure these are the same.
SelectedRow is not a property of CollectionView. I assume both are properties of your ViewModel:
<Grid DataContext="{Binding}">
<DataGrid ItemsSource="{Binding CollectionView}"
SelectedItem="{Binding SelectedRow}" />
</Grid>

Resources