I'm writing a tag control, based on the listbox.
It is displaying the ListBox items using following template:
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<local:TagControl Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Remove="RemoveItem" />
</DataTemplate>
</Setter.Value>
</Setter>
I've noticed that when I update TagControl's text, the original item in the ListBox does not get updated. I'm using ObservableCollection<string> as items source.
TagControl implements INotifyPropertyChanged and calls the event.
What am I doing wrong?
I've reproduced your problem and can offer a solution. The ObservableCollection<string> is enumerated using IEnumerable which is read-only.
If you replace the ObservableCollection<string> with ObservableCollection<DataItem> where
public class DataItem
{
public string Name{get;set;}
}
and then bind to Name in your DataTemplate, the enumerated DataItem is read-only, but the Name property is read-write and will be updated when you edit the text in the list item.
Related
I implemented editable ListBox items like it is posted in this answer Inline editing TextBlock in a ListBox with Data Template (WPF)
.
But the new value does not get updated in the ItemsSource object of my ListBox.
This is the XAML:
<ListBox Grid.Row="2" Name="ds_ConfigProfiles" ItemsSource="{Binding ConfigProfiles}" SelectedItem="{Binding ActiveConfigProfile}" IsSynchronizedWithCurrentItem="True" Panel.ZIndex="-1">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- TODO: this is meant for allowing edit of the profile names, but the new name does not get stored back to ConfigProfiles -->
<local:TextToggleEdit Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="40" Height="23" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is the ConfigProfiles property in the view model:
/// <summary>Configuration profiles that were found in the active storage path</summary>
public ObservableCollection<string> ConfigProfiles { get; private set; } = new ObservableCollection<string>();
Did I understand something wrong?
May it be the reason, that the items source is of type ObservableCollection<string> instead of ObservableCollection<ProperClassImplementation> (which is of legacy reasons).
I am relatively new to WPF and am out of ideas on how to debug this.
May it be the reason, that the items source is of type ObservableCollection<string> instead of ObservableCollection<ProperClassImplementation> (which is of legacy reasons).
Yes, exactly. You can't modify a string since it is immutable. You need to bind to a string property of a class which means that you need to replace the ObservableCollection<string> with an ObservableCollection<ProperClassImplementation>.
I am afraid the binding engine won't replace the string in the ObservableCollection<string> with a new string for you if that's what you had hoped for.
Background:
I have a Datagrid with some Measurements and this Measurements we can Approve and Block.
Now we have for this a new Type, like "Cancelled". But this Type is only needed by Server and for displaying it to Customer.
But the Customer should not be able to select this "Cancelled" but the other 2 Types he should have to select.
The List get all different elements from Database (3 entries).
Firstly i tried to remove the Cancelled Item from the ApprovementCollection, but then it displayed a empty field instead of "Cancelled".
Question:
Is it possible, to disable only one of this 3 Items in the Bounded List of the Itemsource Property from the DataGridComboBoxColumn?
Disabled or Not Displayed in the Selection Menu is that what i have to do.
(Only "Freigabe" and "GESPERRT")
View:
<DataGridComboBoxColumn ClipboardContentBinding="{x:Null}"
DisplayMemberPath="ApprovementText"
Header="{x:Static trans:Translations.ClearenceHeader}"
ItemsSource="{Binding Source={StaticResource PossibleComponentMeasurementApprovements}}"
SelectedItemBinding="{Binding Approvement,
UpdateSourceTrigger=PropertyChanged}" />
Viewmodel:
private IEnumerable<ComponentMeasurementApprovement> possibleComponentMeasurementApprovements;
public IEnumerable<ComponentMeasurementApprovement> PossibleComponentMeasurementApprovements {
get { return possibleComponentMeasurementApprovements; }
set {
possibleComponentMeasurementApprovements = value;
OnPropertyChanged();
}
}
Thanks for your Help
This is possible writing a DataGridTemplateColumn for your cell instead of using the DataGridComboBoxColumn. Just add a property Enabled to your ComponentMeasurementApprovement class. This property indicates if the user is allowed to select the approvement.
Now create a new DataGridTemplateColumn containing a ComboBox as template. It is possible to bind IsEnabled of every ComboBox item to a proeprty by styling them via ItemContainerStyle.
Here is the code:
<DataGridTemplateColumn Header="CustomCell">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={x:Static local:ViewModel.PossibleComponentMeasurementApprovements}}"
DisplayMemberPath="ApprovementText"
SelectedItem="{Binding Approvement}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding Enabled}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is the result:
Since the second item is disabled, it's not possible to select it but an already selected item keeps selected.
I am trying to bind a ListBox to another ListBox within the same window. The left hand sided Listbox has data in it that one can select. But I want a user to be able to click on the item(s) in the left hand listbox and those same item(s) would be displayed in the other listbox on the right hand side.
EDITED: Of course you can bind a UI property to another UI property (Dependency Property actually) using ElementName, but I recommend to bind the properties to one view model. See a simplified example below.
View model:
public ObservableCollection<ItemObject> Items { get; set; }
public ObservableCollection<ItemObject> SelectedItems { get; set; }
Left:
<ListBox ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}" />
(Note that there is no SelectedItems dependency property actually. See question like: Select multiple items from a DataGrid in an MVVM WPF project)
Right:
<ListBox ItemsSource="{Binding SelectedItems}" />
This works fine. Furthermore, with this approach, the list on the right hand can be customized with ease (eg order, filter, ... by using CollectionView).
private ICollectionView _collectionView;
private ICollectionView _CollectionView {
get { return _collectionView
?? (_collectionView = CollectionViewSource.GetDefaultView(SelectedItems)); }
}
public ICollectionView FilteredItems {
get { _CollecitionView.Filter(...); }
}
<ListBox ItemsSource={"Binding FilteredSelectedItems"} />
Such an MVVM approach is sometimes laborious, but eventually found as beneficial.
You name the first listbox, then any other control on the xaml will bind to that control using it's name in the ElementName attribute of the binding.
For example there are two listboxes and one text box. The top listbox has multiselections and those selection(s) are shown on the lower listbox. While the textbox only gets the first item selected.
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<converters:PathToFilenameConverter x:Key="FilenameConverter" />
<x:Array x:Key="FileNames" Type="system:String">
<system:String>C:\Temp\Alpha.txt</system:String>
<system:String>C:\Temp\Beta.txt</system:String>
<system:String>C:\Temp\Gamma.txt</system:String>
</x:Array>
</StackPanel.Resources>
<ListBox Name="lbFiles"
SelectionMode="Multiple"
ItemsSource="{StaticResource FileNames}"
Margin="10"/>
<ListBox ItemsSource="{Binding SelectedItems, ElementName=lbFiles }" Margin="10" />
<TextBlock Text="{Binding SelectedItem,
ElementName=lbFiles,
Converter={StaticResource FilenameConverter}}"
Margin="10" />
</StackPanel>
Note...the code is binding using the SelectedItems property for the lower list box and not SelectedItem used by the TextBlock.
As an aside, another answer has the use of an ObservableCollection, that is not needed unless the array is dynamically changing; otherwise any array can be used. Depending on loading, say from a VM, it may need to adheres to the INotifyPropertyChanged.
I know that the ListBox has both a SelectedItem and SelectedItems attribute and that only the SelectedItem attribute can be used with databinding. However, I've read in multiple locations that by setting up a setter like so
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
and then by adding the following property
public IEnumerable<Item> SelectedItems
{
get
{
return Items.Where(x => x.IsSelected);
}
}
I can use the SelectedItems property to get the all items that were selected. I mainly have two questions regarding this:
Where does the Item and Items in the property come from? I've not been able to find a using directive that will remove the error preventing me from using it.
Is this the recommended way to do what I'm trying to achieve. If not, then what would you recommend?
Oh, before I forget I thought I should mention that I'm using both MVVM Light and Fody's PropertyChanged.
I'm not quite sure about the articles and their exact solution for doing so but my speculations is this:
The whole page has a ViewModel named MyPageViewModel.
MyPageViewModel has an ObservableCollection named Items.
Item is ViewModel type (derived from DependencyObject)
Item has a DependencyProperty named IsSelected
Given these assumptions you can see where can all things fit.
If the DataContext of the whole page is MyPageViewModel, then in this xaml code first IsSelected refers to a property of ListBoxItem, and the second one refers to a property in ViewModel of Item.
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
//This is inside MyPageViewModel
//Item is a ViewModel type
public IEnumerable<Item> SelectedItems
{
get
{
//Items is ObservableCollection<Item>
return Items.Where(x => x.IsSelected);
}
}
//This is also inside MyPageViewModel
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items { get { return _items; } }
The whole scenario can go the other way too. I mean it can be implemented in View instead of ViewModel. Maybe derive from ListBox and override a few things including SelectedItems. But adding these sorts of complexities are better done to ViewModel than View.
I have a list box in WPF as under
<ListBox Name="lstName" DisplayMemberPath ="ListName" ToolTip="{Binding Path=ListName}" />
My requirement is that what ever items I am displaying in the listbox, should also appear in the tooltip. i.e. if the items are say "Item1", "Item2" etc. then when the user will point(hover) to "Item1" through mouse, the toolltip should display "Item1". Same for others
So my DisplayMemberPath is set to the Property which I am supposed to display (and it is coming properly). However, the tooltip is not coming at all.
The entity is as under
public class ItemList
{
public string ListName { get; set; }
}
The binding is happening as under
this.lstName.ItemsSource = GetData(); // Assume that the data is coming properly
Instead of setting the ToolTip property on the ListBox, set it on the ListBoxItems by applying a style:
<ListBox Name="lstName" DisplayMemberPath="ListName">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ToolTip" Value="{Binding ListName}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
That way, each ListBoxItem will have its own tooltip that displays the value for that item.
Since you are setting the ItemsSource on the ListBox directly, you probably haven't set a DataContext, so the Binding won't work there. If you do set the DataContext to the list, then that binding would display the currently selected item as the tooltip no matter where the mouse was on the ListBox.