Sorting a bound observable collection - c#

I have an MVVM collection that I "know" is reordered in the VM but not showing in it's new order in the view. Given code similar to that below, should I expect the the list to re-display in a new sort without manipulating the CollectionViewSource?
xaml
<Menu Name="_mainMenu" Height="22" >
<MenuItem Header="Language"
ItemsSource="{Binding AvailableCultures}" >
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem IsCheckable="True"
IsChecked="{Binding IsSelected, Mode=TwoWay}"
Header="{Binding DisplayName}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
vm
public ObservableCollection<OptionLocalizedViewModel<CultureInfo>>
AvailableCultures { get; private set; }
private void OnSelectionChange(OptionLocalizedViewModel<CultureInfo> option)
{
...
var sorted = AvailableCultures.OrderBy(x => x.DisplayName);
AvailableCultures =
new ObservableCollection<OptionLocalizedViewModel<CultureInfo>>(sorted);
NotifyOfPropertyChange(() => AvailableCultures);
}
UPDATE
The order is being changed, but not as expected (and not what the debugger shows the newly sorted ObsCollection to be). I also tried ditching the ObsCollection in favor of binding to an IEnumerable directly with the exact same result.
Does anyone see a pattern that suggests a fix??
1) initial load, looks as it should
2) select Spanish, so should be Espanol first but isn't
3) back to English, but somehow English is last. How did this get flipped?
4) back to Spanish, same as try (2)

Try using ListCollectionView instead :
ListCollectionView LCV = new ListCollectionView(YourObservableCollection);
LCV.GroupDescriptions.Add(new PropertyGroupDescription("PropertyName"));
YourDataBoundProperty = LCV;
You can refer to this article for more detail.

This should theoretically work, just be sure INotifyPropertyChanged is actually getting fired correctly as it is necessary when replacing the entire collection with a different one rather than just altering it's contents.

Related

No update of ListBox.ItemsSource after implementing editable ListBox items via DataTemplate

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.

Misbehaving context menu (mvvm)

I've put together what I thought was a context menu in an MVVM setting (I'm using WPF with XAML and C#, using MVVM). Only it's not working, which is why I'm here. I'm getting nothing in my context menu.
The XAML is supposed to call an ICommand in the code behind (or Relay Command since I'm using micro MVVM - same thing basically).
The first thing was to set up an object which the XAML could get the two needed values from - the Header and the Command. The item in question looks like this:
class ContextMenuVM : ObservableObject
{
public string Displayname { get; set; }
public RelayCommand ContextMenuCommand { get; set; }
}
So, rather simple there. These will be used for the bindings in the menu.
The view model here is called 'CharacterListViewModel' and contains an ObservableCollection if these ContextMenuVM objects. That looks like this:
private ObservableCollection<ContextMenuVM> _sceneAddMenu = new ObservableCollection<ContextMenuVM>();
public ObservableCollection<ContextMenuVM> SceneAddMenu
{
get { return _sceneAddMenu; }
set
{
if (_sceneAddMenu != value)
{
_sceneAddMenu = value;
RaisePropertyChanged("SceneAddMenu");
}
}
}
The ObservableCollection is populated, as follows:
foreach (Scene s in Database.Instance.Scenes)
{
SceneAddMenu.Add(new ContextMenuVM()
{
Displayname = s.SceneName, ContextMenuCommand = new RelayCommand(
() =>
{
MessageBox.Show("Clicked");
})
});
}
Just a test at the moment, but I can say through use of break points that SceneAddMenu contains four items after this code is run (as I would expect).
Well, that's kind of the background code. I suspect it works, although clearly something is broken. My suspicion is the XAML.
The context menu code itself is here:
<ContextMenu x:Key="CharacterMenu" ItemsSource="{Binding SceneAddMenu}">
<ContextMenu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem Header="Edit" Command="{Binding ContextMenuCommand}"></MenuItem>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
Ah, so the obvious problem would be that the data context is not properly set up. Well that's not the case because this context menu replaces another one which utilised a command in the view model (and that worked), so my assumption is that the view model is okay.
For the record, the previous context menu, which works, looked like this:
<ContextMenu x:Key="CharacterMenu">
<MenuItem Header="Edit" Command="{Binding EditCharacter}"/>
</ContextMenu>
And if I put that back in, it works. Since it has a binding to the view model, that would suggest that the data context is not the problem.
The Context menu itself is referenced a little later, like this:
<StackPanel Orientation="Horizontal" Margin="3" ContextMenu="{StaticResource CharacterMenu}">
But since that was also in before with the previous menu (i.e. when it worked), I only include it for completion's sake.
So the SceneAddMenu object (ObservableCollection) is populated. That seems to be fine. Somewhere between the XAML and the view model there must be a problem though. If I put a break point in the 'get' for SceneAddMenu and then right click on the item in question, the break point does not activate.
I am at a bit of a loss on this one. It's my first time creating a context menu using the MVVM method, so it's possible I missed out a step somewhere.
If you read all of this, thanks a lot. If I missed out any information, please let me know.
You shouldn't add a MenuItem to the ItemTemplate of a ContextMenu. You should define an ItemContainerStyle and bind to the Displayname and ContextMenuCommand properties of your class:
<ContextMenu x:Key="CharacterMenu" ItemsSource="{Binding SceneAddMenu}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Displayname}" />
<Setter Property="Command" Value="{Binding ContextMenuCommand} " />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>

How to properly remove Items from a ListView when the ItemTemplate is a User Control?

I tried to follow the example here:
WPF ListBox with self-removing items
It made sense but my issue was, the ListView itself is determining the template used. So it can easily customise the bindings to point to the correct target. I am however using MVVM and am struggling to fit the two together.
Example, if the template was:
<ListBox.ItemTemplate>
<DataTemplate>
<local:MyItemView/>
</DataTemplate>
</ListBox.ItemTemplate>
This suddenly becomes more difficult, as ideally, I want to reuse that view without hard coding the bindings.
I tried to use DependencyProperty to pass the List and the Element through, so I could delete it via command.
<ListBox.ItemTemplate Name="myList">
<DataTemplate>
<local:MyItemView TheList={Binding ElementName=myList, Path=DataContext.List} TheElement={Binding}/>
</DataTemplate>
</ListBox.ItemTemplate>
However, I had binding errors telling me that it couldn't convert the value for TheElement from MyClassViewModel to MyClass. Even if I commented that out TheList was always NULL.
Essentially I want:
class MyDataClass { // pretend there's more here}
class MyDataClassContainer
{
public ObservableCollection<MyDataClass> Items;
public void Add(MyDataClass);
public void Remove(MyDataClass);
}
class MyDataClassEntryViewModel
{
public static readonly DependencyProperty ListItemProperty = DependencyProperty.Register("TheClass", typeof(MyDataClass), typeof(MyDataClassEntryViewModel));
public static readonly DependencyProperty ListContainerProperty = DependencyProperty.Register("TheContainer", typeof(MyDataClassContainer), typeof(MyDataClassEntryViewModel));
public MyDataClass TheClass;
public MyDataClassContainer TheContainer;
public ICommand Delete = new DelegateCommand(RemoveItem);
private function RemoveItem(object parameter)
{
TheContainer.Remove(TheClass);
}
}
With the following templates:
MyDataClassEntryView.xaml
<UserControl>
<Grid>
<Button Content="Delete" Command="{Binding Path=Delete}"/>
</Grid>
</UserControl>
MyDataContainerView.xaml
<UserControl>
<ListView x:Name="listView" ItemsSource="{Binding Path=Container.Items}">
<ListView.ItemTemplate>
<DataTemplate>
<local:MyDataClassEntryView TheClass="{Binding}" TheContainer="{Binding ElementName=listView, Path=DataContext.Container}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
Note: I have omitted most of the superfluous lines, as I'm trying to get a generic answer I can use everywhere. Not a hard coded single solution. I was basically want to keep the MVVM structure strong, without lots of hard coded and wiring in the background. I want to use the XAML as much as possible.
All the other methods I see to do with removing from a list, require all sorts of assumptions, such as using the SelectedIndex/Item, or using a method on the ContainerView itself to take the element as a parameter, cast it, then remove, etc. In short, most solutions are far too hard coded to the given examples. It feels like there should be an easy way to achieve this in WPF.
As the ListView issautomatically creating instances of my sub-ViewModel/Views, it's impossible for me to get any data in apparently. I just want to pass parameters along using bindings, basically.
Your button should look like this:
<Button Content="Delete"
Command="{Binding Path=Delete}"
CommandParameter="{Binding}/>
Then the remove command should look something like this:
private function RemoveItem(object parameter)
{
var item = parameter as MyDataClass
if(item != null)
TheContainer.Remove(item);
}
You do not need to pass the list to the UserControl within the ItemTemplate, since it doesn't need to know about the list at all
Edit:
I read over your question a few times to see what you were confused about so I will try to clarify.
Whether the ListView sets its own template in the Xaml, or you use another UserControl, the datacontext still gets passed down to the item. Regardless of how you decide to template the items, the ItemTemplate will have the datacontext of a single item from the ListView's items list.
I think your confusion comes in with having controls outside being brought in for templating. Think of it as if the Xaml from the control you brought in being cut and pasted into the DataTemplate of the ListView when running the program, and then it is really no different from being hard coded in there.
You cannot reach outside of a DataTemplate with Element bindings like you have tried.
Instead you need to use a relativesource like this.
<local:MyItemView TheList="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}, Path=DataContext.List}" />

WPF: Reusing control with slightly different call on ViewModel?

I have a control, ActionRequiredControl.xaml that includes a combo-box with a list of enums. This control has ActionRequiredViewModel as it's DataContext, and the following call is used to populate the combobox:
public IEnumerable<ActionType> Actions
{
get
{
return Enum.GetValues(typeof(ActionType)).Cast<ActionType>();
}
}
And the combo-box is as follows.
<StackPanel Orientation="Vertical">
<Label>Action:</Label>
<ComboBox Width="170" MinHeight="45" Margin="5,0,5,5" Padding="5"
SelectedItem="{Binding Action, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Actions, Mode=OneTime}"
VerticalContentAlignment="Center" IsEnabled="{Binding IsUsed}"/>
</StackPanel>
This control is used in two places currently (There is more to it than just the combobox) - however, I would like to re-use it in a third place, only have the "Actions" function return a different list of .
What's the best way to go about this? The only thing I can think of is creating a new class that inherits from ActionRequiredViewModel and overrides the Actions method.
The simplest solution was the one I indicated in the question - I created a new class that inherited from ActionRequiredViewModel and overrode the "Actions" method to return a different list of enums.
Yes, creating an additional ViewModel will be the simplest solution.
I was in a similar situation some time ago, my first implementation (using MVVM light) was using two VMs, and registering both in the ViewModelLocator and providing a property for each:
SimpleIoc.Default.Register<ViewModel1>();
SimpleIoc.Default.Register<ViewModel2>();
public ViewModel ViewModel1Instance
{
get
{
return ServiceLocator.Current.GetInstance<ViewModel1>();
}
}
public ViewModel ViewModel2Instance
{
get
{
return ServiceLocator.Current.GetInstance<ViewModel2>();
}
}
And then in XAML:
<local:MyUserControl DataContext="{Binding ViewModel1Instance,
Source={StaticResource Locator}}" ...
However, to be a bit more flexible, I replaced this with another solution later, which provided better customization.
I played a bit with your requirements and came to this solution:
Creating a dependency property in the code behind of the user control as a proxy:
public static readonly DependencyProperty ComboBoxItemsProperty =
DependencyProperty.Register("ComboBoxItems",
typeof(IEnumerable<object>), typeof(TestUserControl));
public IEnumerable<object> ComboBoxItems
{
get
{
return (IEnumerable<object>)GetValue(ComboBoxItemsProperty);
}
set
{
SetValue(ComboBoxItemsProperty, value);
}
}
In the XAML of the user control:
...ItemsSource="{Binding ComboBoxItems, Mode=OneWay, RelativeSource=
{RelativeSource AncestorType={x:Type UserControl}}}"...
In the main view model:
public IEnumerable<object> Items
{
get
{
return Enum.GetValues(typeof(ActionType)).Cast<object>();
}
}
In the main XAML:
<local:TestUserControl ComboBoxItems="{Binding DataContext.Items,
Mode=OneTime,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
</local:TestUserControl>
This worked for me, but using object instead of the custom type is not the most elegant solution.
PS: This is my very first answer on SO, (and no questions yet from me...).
Since I used SO for many years as my primary source of tips and tricks, I thought I can TRY to give something back...
And of course, I admit: trying to answer is not the worst method of learning a new topic (and WPF is fairly new to me, so please donĀ“t blame me for a suboptimal answer :-)

Items collection must be empty before using ItemsSource. Telerik Treeview

Im getting this error while trying to giva my treeview an itemsource
"Items collection must be empty before using ItemsSource."
I have checked a lot of solutions and I cant seem to find a way to solve this. Here are my code snippets:
XAML:
<HierarchicalDataTemplate x:Key="Category">
<TextBlock Text="{Binding Name}">
</TextBlock>
</HierarchicalDataTemplate>
XAML
<telerik:RadTreeView x:Name="treeview" IsDragDropEnabled="True" HorizontalAlignment="Left" Height="250" Margin="10,10,0,-3" VerticalAlignment="Top" Width="190" IsManipulationEnabled="True" IsLoadOnDemandEnabled="True" LoadOnDemand="treeview_LoadOnDemand" IsExpandOnSingleClickEnabled="True" ItemTemplate="{StaticResource Category}">
</telerik:RadTreeView>
C# - Giving the treeview a data source:
Data d = new Data();
treeview.ItemsSource = d.Get_Categories();
C# - My database query:
public List<Category> Get_Categories()
{
using (var context = new ProcessDatabaseEntities())
{
return context.Category.ToList();
}
}
Category only has two properties, Name and ID. I know that the itemsource-list is not empty when I assign it. So it's probably something wrong with my XAML-code. Thank you in advance!
I believe that your problem is a common one. Basically, you cannot use both the TreeView.ItemsSource and the TreeView.Items properties together... you must choose one way or the other. Usually this problem manifests itself because a developer has done something like this...:
<TreeView Name="TreeView" ItemsSource="{Binding SomeCollection}" ... />
... and then tried to do something like this in the code behind:
TreeView.Items.Add(someItem);
The solution in that case would be to manipulate the data bound collection instead of the TreeView.Items collection:
SomeCollection.Add(someItem);
However, in your case (and it's a little bit difficult to guess without seeing your code), you have probably done the second part first (set or manipulated the Items property) and then tried to set the ItemsSource property. Your solution is the same... use one method of editing the items or the other... not both.

Categories

Resources