InvalidOperationException when jumping with windows phone LongListSelector - c#

I have already made some view with a LongListSelector, but now I have a really strange issue...
In fact, when I try to "tap" on my GroupHeader to my JumpList, my application throw an InvalidOperationException.
My container Model:
public class NextArrivalContainer<T> : List<T>
{
#region Properties
public string Destination { get; private set; }
public string Line { get; private set; }
#endregion //Properties
public NextArrivalContainer(string key, List<T> items)
: base(items)
{
if (key.Contains(' '))
{
int index = key.IndexOf(' ');
Line = key.Substring(0, index);
Destination = key.Substring(index + 1);
}
else
{
Destination = string.Empty;
Line = string.Empty;
}
}
}
My ViewModel Property:
private List<Model.NextArrivalContainer<string>> _trams;
public List<Model.NextArrivalContainer<string>> Trams
{
get { return (_trams); }
set
{
if (_trams != value)
{
_trams = value;
OnNotifyPropertyChanged("Trams");
}
}
}
My ViewModel where I fill the Property:
var tramGrouped = from item in trams
orderby item.Destination
group item by item.Destination into tramGroupedByDestination
select new Model.NextArrivalContainer<string>(tramGroupedByDestination.Key,
(from item in tramGroupedByDestination select item.Time).ToList());
Trams = new List<Model.NextArrivalContainer<string>>(tramGrouped);
And my View:
<phone:PivotItem Header="Tram">
<phone:LongListSelector ItemsSource="{Binding Trams}"
ItemTemplate="{StaticResource ItemTemplate}"
GroupHeaderTemplate="{StaticResource TramHeaderTemplate}"
JumpListStyle="{StaticResource JumpStyle}"
IsGroupingEnabled="True"
HideEmptyGroups="True"
LayoutMode="Grid" GridCellSize="70, 50"/>
</phone:PivotItem>
And his template:
<Style TargetType="phone:LongListSelector" x:Key="JumpStyle">
<Setter Property="LayoutMode" Value="List"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource backgroundConverter}}"
Width="450" Height="70">
<TextBlock Text="{Binding Destination}"
Foreground="{Binding Converter={StaticResource foregroundConverter}}"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
I am looking forward your answers.
Edit:
I just try something, my view was a UserControl, (I didn't know why I did it, but I did...)
So I try to changes to a phone:PhoneApplicationPage and It fix my issue.
If somebody can explain me why.
I hope that my question will help another developers.

Related

MuliSelectCombobox in WPF/C# to show Displaynames of all selected Items

A Combobox binds to a list of custom combobox items where each Item contains a checkbox together with an Image. The user can click either the image or the checkbox to select the item.
Each item contains a relative image path, the Selection-State. The viewmodel generates the list CustomCheckboxItems which the view then binds to.
Now i want to show the Displaynames of all selected items in the Combobox as ... selected items ... together. How can i achieve that? I tried to attach a contentpresenter to the combobox without success since i do not know exactly where to attach it to. Also writing control templates did not do the trick for me.
In the end the combobox should look something like this (Link goes to cdn.syncfusion.com). The ViewModel contains already a comma separated string containing the selected items. How do i have to change the combobox to behave like this?
View:
<ComboBox ItemsSource="{Binding Path=ViewModel.CustomCheckBoxItems, Mode=OneTime}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:CustomCheckBoxItem}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=ImagePath}">
<Image.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding SelectItem, Mode=OneWay}" />
</Image.InputBindings>
</Image>
<CheckBox VerticalAlignment="Center" VerticalContentAlignment="Center"IsChecked="Binding Selected, Mode=TwoWay}" >
<TextBlock Text="{Binding DisplayName}" IsEnabled="False" VerticalAlignment="Center" />
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The CustomCheckBoxItem Implementation
//INotifyPropertryChanged implementation and other things excluded for brevity
public class CustomCheckBoxItem : INotifyPropertyChanged
{
public CheckboxItem(ItemType item, string imagePath)
{
Item = item;
try{ImagePath = "/" + currentAssemblyName + ";component/Images/" + imagePath;}catch(Exception){}
}
private bool selected;
public bool Selected
{
get => selected;
set
{
selected = value;
NotifyPropertyChanged();
}
}
public ICommand SelectItem => new RelayCommand(() =>
{
if (isInit)
{
Selected = !selected;
}
},false);
public string ImagePath { get; }
public string DisplayName => Item.GetDisplayName();
}
The solution is to disconnect the ContentPresenter, that hosts the selected item in the selection box, from the ComboBox.
The ComboBox sets the selection box value internally. There is no chance to modify this behavior. Since the selection box also serves as input field for the edit or search mode, this logic is quite complex. From this perspective, the design choice to make the complete related logic of the ComboBox internal makes much sense.
The first option is to simply override the default template of the ComboBox and remove the bindings on the ContentPresenter.ContentTemplate, ContentPresenter.ContentTemplateSelector and ContentPresenter.ContentStringFormat properties.
Then bind the ContentPresenter.Content property to the data source that holds the multi selection display names. The disadvantage is that the complete multi-select logic is outside the ComboBox (in the worst case, it even bleeds into the view model). As the multi-select behavior is part of the control, it should be implemented inside.
The second solution is to extend ComboBox to make the handling of the control more convenient and reusable. You probably don't want your view model to be responsible to participate in the multi-select logic e.g. by tracking selection states in the context of multi-select (which is not of interest from the view model's point of view) and by exposing the display values to represent the multi-select state. You view model should not care about display values or multi-select.
The third option is to implement your custom ComboBox and let it extend MultiSelector. If you require all the ComboBox features, then this solutions can take a while to implement and test properly. If you only need a basic multi-select dropdown control, then the task is quite simple and the cleanest solution.
Other options you may find, like hacking the edit mode to set the ComboBox.Text property, are not recommended as they interfere with the internal behavior: the logic around the IsEditable and IsReadOnly is quite complex. So better don't mess around with to prevent unexpected behavior.
Also, you lose many features like search and edit. If you don't care about those feature, then the best and cleanest solution is the third introduced option to implement a Custom control that extends MultiSelector.
Implementation
The following example implements the second solution. The MultiSelectComboBox extends ComboBox. It internally grabs the ContentPresenter, disconnects it from the ComboBox (by overriding its content and removing the internal templating) and tracks the selection state. The advantage of this solution is that you don't have to "hack" the edit mode of the ComboBox. Therefore, the edit mode feature remains untouched. This solution simply changes the displayed values in the default toggle box. Even the default single.select behavior remains intact.
The multi-select state is realized by overriding the ComboBoxItem template to replace the ContentPresenter with a ToggleButton or alternatively, create a ComboBoxToggleItem (like in the example below) that extends ComboBoxItem to make everything reusable and MVVM ready.
Additionally we need to introduce a custom IsItemSelected property by implementing it as an attached property. This is necessary because the ComboBoxItem.IsSelected property is controlled by the Selector (the superclass of ComboBox). And by making it attached, we can avoid a tight coupling between the MultiSelectComboBox logic and the ComboBoxToggleItem Everything still works with the default ComboBoxItem or any other item container. The Selector is also responsible to ensure that only a single item is selected. But we need multiple items to be selected simultaneously.
This way we can easily track selected items and expose them via a public SelectedItems property.
You can use the ItemContainerStyle to bind the data model's selection property (if existing) to this attached MultiSelectComboBox.IsItemSelected property.
By implementing a custom ComboBoxItem like the ComboBoxToggleItem and hard-coding the CheckBox into its ControlTemplate you are no longer forced to track the visual state in your view model. This offers a clean separation. The visibility of the CheckBox in this example is able to be toggled by handling the ComboBoxToggleItem.IsCheckBoxEnabled property.
Because the ComboBoxToggleItem is basically a ToggleButton, you can select the item without clicking the CheckBox. The CheckBox is now an optional feature that only serves to provide another visual feedback.
If you don't define the ItemTemplate, you can control the displayed value using the common DisplayMemberPath (ComboBox default behavior). The MultiSelectComboBox will pick the value from the designated member and concatenate it with the other selected values.
If you want to display different values for the drop-down panel and the selected content box, use the MultiSelectComboBox.SelectionBoxDisplayMemberPath property to specify the item's source property name (in the same manner like the DisplayMemberPath).
If you don't set neither DisplayMemberPath nor the SelectionBoxDisplayMemberPath, the MultiSelectComboBox will call object.ToString on the data item. This way you can even generate a computed value by overriding ToString on the model. This gives you three options to control the selection box display value, while the MultiSelectComboBox concatenates and displays them.
This way the complete logic that handles the displayed values was moved from view model to the view, where it belongs:
MultiSelectComboBox.cs
public class MultiSelectComboBox : ComboBox
{
public static void SetIsItemSelected
(UIElement attachedElement, bool value)
=> attachedElement.SetValue(IsItemSelectedProperty, value);
public static bool GetIsItemSelected(UIElement attachedElement)
=> (bool)attachedElement.GetValue(IsItemSelectedProperty);
public static readonly DependencyProperty IsItemSelectedProperty =
DependencyProperty.RegisterAttached(
"IsItemSelected",
typeof(bool),
typeof(MultiSelectComboBox),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsItemSelectedChanged));
public string SelectionBoxDisplayMemberPath
{
get => (string)GetValue(SelectionBoxDisplayMemberPathProperty);
set => SetValue(SelectionBoxDisplayMemberPathProperty, value);
}
public static readonly DependencyProperty SelectionBoxDisplayMemberPathProperty = DependencyProperty.Register(
"SelectionBoxDisplayMemberPath",
typeof(string),
typeof(MultiSelectComboBox),
new PropertyMetadata(default));
public IList<object> SelectedItems
{
get => (IList<object>)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof(IList<object>),
typeof(MultiSelectComboBox),
new PropertyMetadata(default));
private static Dictionary<DependencyObject, ItemsControl> ItemContainerOwnerTable { get; }
private ContentPresenter PART_ContentSite { get; set; }
private Dictionary<UIElement, string> SelectionBoxContentValues { get; }
static MultiSelectComboBox() => MultiSelectComboBox.ItemContainerOwnerTable = new Dictionary<DependencyObject, ItemsControl>();
public MultiSelectComboBox()
{
this.SelectionBoxContentValues = new Dictionary<UIElement, string>();
this.SelectedItems = new List<object>();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (TryFindVisualChildElement(this, out ContentPresenter contentPresenter, false))
{
contentPresenter.ContentTemplate = null;
contentPresenter.ContentStringFormat = null;
contentPresenter.ContentTemplateSelector = null;
this.PART_ContentSite = contentPresenter;
}
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
this.SelectedItems.Clear();
MultiSelectComboBox.ItemContainerOwnerTable.Clear();
Dispatcher.InvokeAsync(InitializeSelectionBox, DispatcherPriority.Background);
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
var itemContainer = this.ItemContainerGenerator.ContainerFromItem(item);
MultiSelectComboBox.ItemContainerOwnerTable.Remove(itemContainer);
this.SelectedItems.Remove(item);
}
break;
}
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
this.SelectionBoxContentValues.Clear();
IEnumerable<(object Item, ComboBoxItem? ItemContainer)>? selectedItemInfos = this.ItemContainerGenerator.Items
.Select(item => (Item: item, ItemContainer: this.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem))
.Where(selectedItemInfo => GetIsItemSelected(selectedItemInfo.ItemContainer));
foreach (var selectedItemInfo in selectedItemInfos)
{
string memberPath = this.SelectionBoxDisplayMemberPath
?? this.DisplayMemberPath
?? selectedItemInfo.Item.ToString();
string itemDisplayValue = selectedItemInfo.Item.GetType().GetProperty(memberPath).GetValue(selectedItemInfo.Item)?.ToString()
?? selectedItemInfo.Item.ToString();
this.SelectionBoxContentValues.Add(selectedItemInfo.ItemContainer, itemDisplayValue);
MultiSelectComboBox.ItemContainerOwnerTable.TryAdd(selectedItemInfo.ItemContainer, this);
this.SelectedItems.Add(selectedItemInfo.Item);
}
UpdateSelectionBox();
}
protected override bool IsItemItsOwnContainerOverride(object item) => item is ComboBoxToggleItem;
protected override DependencyObject GetContainerForItemOverride() => new ComboBoxToggleItem();
private static void OnIsItemSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var comboBoxItem = d as ComboBoxItem;
if (MultiSelectComboBox.ItemContainerOwnerTable.TryGetValue(comboBoxItem, out ItemsControl owner))
{
var comboBoxItemOwner = owner as MultiSelectComboBox;
bool isUnselected = !GetIsItemSelected(comboBoxItem);
if (isUnselected)
{
comboBoxItemOwner.SelectionBoxContentValues.Remove(comboBoxItem);
comboBoxOwner.SelectedItems.Remove(comboBoxItem);
UpdateSelectionBox()
}
}
}
private static void UpdateSelectionBox()
{
string selectionBoxContent = string.Join(", ", this.SelectionBoxContentValues.Values);
if (this.IsEditable)
{
this.Text = selectionBoxContent;
}
else
{
this.PART_ContentSite.Content = selectionBoxContent;
}
}
private void OnItemUnselected(object sender, SelectionChangedEventArgs e)
{
foreach (var removedItem in e.RemovedItems)
{
this.SelectedItems.Remove(removedItem);
}
}
private void InitializeSelectionBox()
{
EnsureItemsLoaded();
UpdateSelectionBox();
}
private void EnsureItemsLoaded()
{
IsDropDownOpen = true;
IsDropDownOpen = false;
}
private static bool TryFindVisualChildElement<TChild>(DependencyObject parent,
out TChild resultElement,
bool isTraversingPopup = true)
where TChild : FrameworkElement
{
resultElement = null;
if (isTraversingPopup
&& parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild frameworkElement)
{
resultElement = frameworkElement;
return true;
}
if (TryFindVisualChildElement(childElement, out resultElement, isTraversingPopup))
{
return true;
}
}
return false;
}
}
ComboBoxToggleItem.cs
public class ComboBoxToggleItem : ComboBoxItem
{
public bool IsCheckBoxEnabled
{
get => (bool)GetValue(IsCheckBoxEnabledProperty);
set => SetValue(IsCheckBoxEnabledProperty, value);
}
public static readonly DependencyProperty IsCheckBoxEnabledProperty = DependencyProperty.Register(
"IsCheckBoxEnabled",
typeof(bool),
typeof(ComboBoxToggleItem),
new PropertyMetadata(default));
static ComboBoxToggleItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ComboBoxToggleItem), new FrameworkPropertyMetadata(typeof(ComboBoxToggleItem)));
}
// Add text search selection support
protected override void OnSelected(RoutedEventArgs e)
{
base.OnSelected(e);
MultiSelectComboBox.SetIsItemSelected(this, true);
}
}
Generic.xaml
<Style TargetType="local:ComboBoxToggleItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ComboBoxToggleItem">
<ToggleButton x:Name="ToggleButton"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:MultiSelectComboBox.IsItemSelected)}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding ElementName=ToggleButton, Path=IsChecked}"
Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsCheckBoxEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ContentPresenter />
</StackPanel>
</ToggleButton>
<ControlTemplate.Triggers>
<Trigger SourceName="ToggleButton"
Property="IsChecked"
Value="True">
<Setter Property="IsSelected"
Value="True" />
</Trigger>
<Trigger SourceName="ToggleButton"
Property="IsChecked"
Value="False">
<Setter Property="IsSelected"
Value="False" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage example
DataItem.cs
class DataItem : INotifyPropertyChanged
{
string TextData { get; }
int Id { get; }
bool IsActive { get; }
}
MainViewModel
class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<DataItem> DataItems { get; }
}
MainWIndow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<local:MultiSelectComboBox ItemsSource="{Binding DataItems}"
DisplayMemberPath="TextData"
SelectionBoxDisplayMemberPath="Id">
<local:MultiSelectComboBox.ItemContainerStyle>
<Style TargetType="local:ComboBoxToggleItem">
<Setter Property="local:MultiSelectComboBox.IsItemSelected"
Value="{Binding IsActive}" />
<Setter Property="IsCheckBoxEnabled"
Value="True" />
</Style>
</local:MultiSelectComboBox.ItemContainerStyle>
<local:MultiSelectComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataItem}">
<TextBlock Text="{Binding TextData}" />
</DataTemplate>
</local:MultiSelectComboBox.ItemTemplate>
</local:MultiSelectComboBox>
</Window>
If I am understanding correctly:
You just want all the checked items to be moved to top of combobox
You want comma separated list of selected items shown in closed combobox.
If that is correct, the solution is much simpler than you are thinking.
All that is needed is this:
/// <summary>
/// Sort the items in the list by selected
/// </summary>
private void cmb_DropDownOpened ( object sender, EventArgs e )
=> cmb.ItemsSource = CustomCheckBoxItems
.OrderByDescending ( c => c.Selected )
.ThenBy ( c => c.DisplayName );
/// <summary>
/// Display comma separated list of selected items
/// </summary>
private void cmb_DropDownClosed ( object sender, EventArgs e )
{
cmb.IsEditable = true;
cmb.IsReadOnly = true;
cmb.Text = string.Join ( ",", CustomCheckBoxItems.Where ( c => c.Selected )
.OrderBy ( c => c.DisplayName )
.Select ( c => c.DisplayName )
.ToList () );
}
The key to the solution is just to reorder the list every time the combo is opened you present a re-sorted list.
Every time it is closed, you gather selected and show.
Complete working example:
Given this XAML:
<ComboBox Name="cmb" DropDownOpened="cmb_DropDownOpened">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding Selected, Mode=TwoWay}" >
<TextBlock Text="{Binding DisplayName}" IsEnabled="False" VerticalAlignment="Center" />
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
and this class (removed notify for this sample, since not needed)
public class CustomCheckBoxItem
{
public string Item { get; set; }
private const string currentAssemblyName = "samplename";
public CustomCheckBoxItem ( string item, string imagePath )
{
Item = item;
try
{ ImagePath = "/" + currentAssemblyName + ";component/Images/" + imagePath; }
catch ( Exception ) { }
}
public bool Selected { get; set; }
public string ImagePath { get; }
public string DisplayName => Item;
}
Then to test I just used this:
public ObservableCollection<CustomCheckBoxItem> CustomCheckBoxItems { get; set; }
= new ObservableCollection<CustomCheckBoxItem> ()
{
new ( "Item 1", "image1.png" ),
new ( "Item 2", "image2.png" ),
new ( "Item 3", "image3.png" ),
new ( "Item 4", "image4.png" ),
new ( "Item 5", "image5.png" ),
new ( "Item 6", "image6.png" ),
new ( "Item 7", "image7.png" ),
};
public MainWindow ()
{
InitializeComponent ();
cmb.ItemsSource = CustomCheckBoxItems;
}
/// <summary>
/// Sort the items in the list by selected
/// </summary>
private void cmb_DropDownOpened ( object sender, EventArgs e )
=> cmb.ItemsSource = CustomCheckBoxItems
.OrderByDescending ( c => c.Selected )
.ThenBy ( c => c.DisplayName );
/// <summary>
/// Display comma separated list of selected items
/// </summary>
private void cmb_DropDownClosed ( object sender, EventArgs e )
{
cmb.IsEditable = true;
cmb.IsReadOnly = true;
cmb.Text = string.Join ( ",", CustomCheckBoxItems.Where ( c => c.Selected )
.OrderBy ( c => c.DisplayName )
.Select ( c => c.DisplayName )
.ToList () );
}
Inspired by the post linked in the comments, I wondered how far I'd get using the base WPF building blocks, but without messing with the ComboBox' template itself. The answer from Heena uses a WrapPanel to show a (filtered) SelectedItems collection that updates on the fly.
<ListBox Background="Transparent" IsHitTestVisible="False" BorderBrush="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled" BorderThickness="0" ItemsSource="{Binding ElementName=lst,Path=SelectedItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ContentData}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I figured a DataTemplateSelector, to differentiate between the SelectedItemTemplate and the DropdownItemsTemplate, in combination with a CollectionView, that has a filter applied, should get us there.
CheckBoxItem.cs
public class CheckboxItem : ViewModelBase
{
public CheckboxItem(string name, string imagePath)
{
DisplayName = name;
ImagePath = imagePath;
SelectItem = new DummyCommand();
}
public string ImagePath { get; }
public string DisplayName { get; }
public ICommand SelectItem { get; }
private bool selected;
public bool Selected
{
get => selected;
set
{
selected = value;
OnPropertyChanged();
ItemSelectionChanged?.Invoke(this, new EventArgs());
}
}
public event EventHandler ItemSelectionChanged;
}
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Items = new ObservableCollection<CheckboxItem>
{
new CheckboxItem("smile", "/Images/Smiley.png"),
new CheckboxItem("wink", "/Images/Wink.png"),
new CheckboxItem("shocked", "/Images/Shocked.png"),
new CheckboxItem("teeth", "/Images/Teeth.png"),
};
SelectedItems = (CollectionView)new CollectionViewSource { Source = Items }.View;
SelectedItems.Filter = o => o is CheckboxItem item && item.Selected;
foreach(var item in Items)
{
item.ItemSelectionChanged += (_, _) => SelectedItems.Refresh();
}
// Set a (dummy) SelectedItem for the TemplateSelector to kick in OnLoad.
SelectedItem = Items.First();
}
public ObservableCollection<CheckboxItem> Items { get; }
public CollectionView SelectedItems { get; }
public CheckboxItem SelectedItem { get; }
}
ComboBoxTemplateSelector.cs
public class ComboBoxTemplateSelector : DataTemplateSelector
{
public DataTemplate SelectedItemTemplate { get; set; }
public DataTemplate DropdownItemsTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var itemToCheck = container;
// Search up the visual tree, stopping at either a ComboBox or a ComboBoxItem (or null).
// This will determine which template to use.
while (itemToCheck is not null and not ComboBox and not ComboBoxItem)
itemToCheck = VisualTreeHelper.GetParent(itemToCheck);
// If you stopped at a ComboBoxItem, you're in the dropdown.
return itemToCheck is ComboBoxItem ? DropdownItemsTemplate : SelectedItemTemplate;
}
}
MainWindow.xaml
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d"
Title="MultiSelectComboBox" Height="350" Width="400">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ComboBoxTemplateSelector x:Key="ComboBoxTemplateSelector">
<local:ComboBoxTemplateSelector.SelectedItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding DataContext.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}"
BorderThickness="0" IsHitTestVisible="False" BorderBrush="Transparent" Background="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:CheckboxItem}">
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</local:ComboBoxTemplateSelector.SelectedItemTemplate>
<local:ComboBoxTemplateSelector.DropdownItemsTemplate>
<DataTemplate DataType="{x:Type local:CheckboxItem}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=ImagePath}" Height="50">
<Image.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding SelectItem, Mode=OneWay}" />
</Image.InputBindings>
</Image>
<CheckBox VerticalAlignment="Center" VerticalContentAlignment="Center" Margin="20 0"
IsChecked="{Binding Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" />
</CheckBox>
</StackPanel>
</DataTemplate>
</local:ComboBoxTemplateSelector.DropdownItemsTemplate>
</local:ComboBoxTemplateSelector>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Path=Items}"
SelectedItem="{Binding SelectedItem, Mode=OneTime}"
ItemTemplateSelector="{StaticResource ComboBoxTemplateSelector}"
Height="30" Width="200" Margin="0 30 0 0"
VerticalAlignment="Top" HorizontalAlignment="Center">
</ComboBox>
</Grid>
</Window>
The ViewModel contains already a comma separated string containing the selected items. How do i have to change the combobox to behave like this?
You could swap
<local:ComboBoxTemplateSelector.SelectedItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding DataContext.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}"
BorderThickness="0" IsHitTestVisible="False" BorderBrush="Transparent" Background="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:CheckboxItem}">
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</local:ComboBoxTemplateSelector.SelectedItemTemplate>
With
<local:ComboBoxTemplateSelector.SelectedItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.CommaSeperatedString, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" />
</DataTemplate>
</local:ComboBoxTemplateSelector.SelectedItemTemplate>
You could also do without the CommaSeperatedString property and combine the CollectionView with Greg M's string.Join approach inside a converter:
<local:ComboBoxTemplateSelector.SelectedItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.SelectedItems,
RelativeSource={RelativeSource AncestorType={x:Type ComboBox}},
Converter={StaticResource ItemsToCommaSeparatedStringConverter}}" />
</DataTemplate>
</local:ComboBoxTemplateSelector.SelectedItemTemplate>
Resources:
WrapPanel idea:
CheckComboBox : How to prevent a combobox from closing after a selection is clicked?
Using different templates:
Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?
Filtering ItemsSource:
custom search for combobox
Smileys:
https://pngimg.com/images/miscellaneous/smiley

How to properly bind multiple ViewModels when using DataTemplateSelector [duplicate]

This question already has answers here:
How to use DataTemplateSelector with ContentControl to display different controls based on the view-model?
(2 answers)
Closed 4 years ago.
I'm trying to write a simple dialog that would accept a value in a SpinEdit or a text in a TextEdit. I'm using multiple VMs and I made a selector that should view a proper control based on the logic in the c++/cli file.
XAML:
xmlns:local="clr-namespace:asd"
Title="{Binding Path=Title, Mode=OneTime}"
<dx:DXWindow.Resources>
<DataTemplate x:Key="TInputValueVM" DataType="{x:Type local:TInputValueVM}">
<dxe:SpinEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}" />
</DataTemplate>
<DataTemplate x:Key="TInputTextVM" DataType="{x:Type local:TInputTextVM}">
<dxe:TextEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
MaskType="RegEx" Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"/>
</DataTemplate>
<local:PropertyDataTemplateSelector x:Key="templateSelector"
DataTemplate_Value="{StaticResource TInputValueVM}"
DataTemplate_Text="{StaticResource TInputTextVM}" />
</dx:DXWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" >
<Label x:Uid="Label" MinHeight="24" MinWidth="60" Content="Value" />
<ContentControl Content="{Binding Path=Whoami}" ContentTemplateSelector="{StaticResource templateSelector}" />
</StackPanel>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" />
</StackPanel>
</Grid>
In c# I have a base VM and two VMS that extend it, one for values and one for text. The rest of the properties stay the same.
C#
namespace asd
{
public class TInputBaseVM : ViewModelBase
{
private string m_sTitle;
private string m_sMask;
private int m_nInputLenght;
private string m_sWhoami;
public TInputBaseVM(string A_sTitle, string A_sMask, int A_nInputLength)
{
m_sTitle = A_sTitle;
m_sMask = A_sMask;
m_nInputLenght = A_nInputLength;
}
protected string Title
{
get { return m_sTitle; }
set { SetProperty(ref m_sTitle, value, () => Title); }
}
protected string Mask
{
get { return m_sMask; }
set { SetProperty(ref m_sMask, value, () => Mask); }
}
protected int InputLength
{
get { return m_nInputLenght; }
set { SetProperty(ref m_nInputLenght, value, () => InputLength); }
}
protected string Whoami
{
get { return m_sWhoami; }
set { SetProperty(ref m_sWhoami, value, () => Whoami); }
}
}
public class TInputValueVM : TInputBaseVM
{
public TInputValueVM(string A_sTitle, string A_sMask, int A_nInputLength, double A_nValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_nValue;
Whoami = "Value";
}
private double m_nValue;
public double Value
{
get { return m_nValue; }
set { SetProperty(ref m_nValue, value, () => Value); }
}
}
public class TInputTextVM : TInputBaseVM
{
public TInputTextVM(string A_sTitle, string A_sMask, int A_nInputLength, string A_sValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_sValue;
Whoami = "Text";
}
private string m_sValue;
public string Value
{
get { return m_sValue; }
set { SetProperty(ref m_sValue, value, () => Value); }
}
}
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate_Value { get; set; }
public DataTemplate DataTemplate_Text { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selector = item as string;
if(selector == "Value")
return DataTemplate_Value;
return DataTemplate_Text;
}
}
}
In c++/cli I create an object of a proper VM and I'd like the WPF to automatically update the view to either spinedit or textedit, however I'm not sure how to properly bind the properties from the C#. If I explicitly type 'Value' in the Content property of the ContentControl then it displays the spinEdit but I don't know how to bind it so it automatically takes the correct property.
EDIT: I'm adding c++/cli code to show how I choose different VMs
C++/cli:
bool TSignalNumberPositionDialogCLR::StartDialog(TSignalNumberPositionSupport& A_Attributes, HWND A_hwndParent, LPTSTR String)
{
try
{
TInputValueVM ^oExchange_Value;
TInputTextVM ^oExchange_Text;
int inputFormat = A_Attributes.GetInputFormat();
if(inputFormat)
oExchange_Text = gcnew TInputTextVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), gcnew System::String(A_Attributes.GetInitialText()));
else
oExchange_Value = gcnew TInputValueVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), A_Attributes.GetInitialValue());
Dialogs::TSignalNumberPositionDialog^ dialog = gcnew Dialogs::TSignalNumberPositionDialog();
if(inputFormat)
dialog->DataContext = oExchange_Text;
else
dialog->DataContext = oExchange_Value;
dialog->ShowDialog();
if(dialog->DialogResult)
{
CString nValue;
if(inputFormat)
nValue = oExchange_Text->Value;
else
nValue = ((Decimal)oExchange_Value->Value).ToString("F2", CultureInfo::InvariantCulture);
A_Attributes.UpdateValue(nValue, String, A_Attributes.GetInputLength());
return true;
}
return false;
}
catch(Exception^ e)
{
e;
}
}
based on the 'inputFormat' variable I want to display different controls in the dialog.
EDIT: Based on #Clemens comments I got rid of the selector sectionand the x:Key property in the DataTemplates. I changed the content opf the Content property to Content="{Binding}" and it somehow works. The moment I create a VM it selects the correct one.
Based on your comment. Let me improve my answer. As you are facing issue in VM selection. so plesae concentrate how I assigned VM to datatemplate. Although it is done in very basic way, you can handle it if you you are using MVVM packages.
I have created 2 data template and 2 vms and each vm is bound to datatemplate. To verify, I have a combobox, which will select datatemplate based on selected value.
Here is Sample VM
public class VM : System.ComponentModel.INotifyPropertyChanged
{
private string title;
private SolidColorBrush background;
public string Title { get => title; set { title = value; RaisePropertyChanged(); } }
public SolidColorBrush Background { get => background; set { background = value; RaisePropertyChanged(); } }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class VM1: VM
{
public VM1()
{
Title = "This is VM1";
Background = Brushes.Yellow;
}
}
public class VM2: VM
{
public VM2()
{
Title = "This is VM2";
Background = Brushes.Orange;
}
}
Now check for resources
<local:VM1 x:Key="VM1"/>
<local:VM2 x:Key="VM2"/>
<DataTemplate x:Key="DT1">
<Grid DataContext="{StaticResource VM1}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="DT2">
<Grid DataContext="{StaticResource VM2}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<Style TargetType="ContentControl" x:Key="contentStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template1">
<Setter Property="ContentTemplate" Value="{StaticResource DT1}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template2">
<Setter Property="ContentTemplate" Value="{StaticResource DT2}" />
</DataTrigger>
</Style.Triggers>
</Style>
and finally I have combobox and content control just to verify
<ComboBox Name="cmbo"/>
<ContentControl Style="{StaticResource contentStyle}"/>
where cmbo.ItemsSource = new List { "Template1", "Template2" };
Hope you got the point

DataGrid loses selection

There is a collection of categories with products.
Each category is represented in the interface by the AvalonDock tab, which has a DataGrid with products.
Now when switching from tab to tab, DataGrid updates the collection every time. If you select a pair of rows in the table on the first tab, switch to the second tab and return to the first one, the selection disappears.
What could be the problem?
XAML:
<xcad:DockingManager DocumentsSource="{Binding Examples}">
<xcad:DockingManager.LayoutItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Content.Items}"
SelectionMode="Extended" />
</DataTemplate>
</xcad:DockingManager.LayoutItemTemplate>
<xcad:LayoutRoot />
</xcad:DockingManager>>
Code-behind:
public partial class MainWindow : Window
{
public class Example
{
public List<int> Items { get; } = new List<int>();
public Example()
{
for (var i = 0; i < 10; i++)
{
Items.Add(i);
}
}
}
public List<Example> Examples { get; } = new List<Example>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
Examples.Add(new Example());
Examples.Add(new Example());
}
}
As #nobody suggested, switching between tabs seems to update the layout, and the selection state is lost. If UI can't persist the selection state, then you can use the next layer i.e. presentation or view-model to do the same.
In this case, adding a IsSelected property to view-model item and a binding to ListViewItem should do the trick.
XAML:
<Grid>
<xcad:DockingManager DocumentsSource="{Binding Examples}">
<xcad:DockingManager.DocumentHeaderTemplate>
<DataTemplate>
<TextBlock Text="Doc" />
</DataTemplate>
</xcad:DockingManager.DocumentHeaderTemplate>
<xcad:DockingManager.LayoutItemTemplate>
<DataTemplate>
<ListBox
DisplayMemberPath="Value"
ItemsSource="{Binding Content.Items}"
SelectionMode="Extended">
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="LightBlue" />
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
</ListBox>
</DataTemplate>
</xcad:DockingManager.LayoutItemTemplate>
<xcad:LayoutRoot />
</xcad:DockingManager>
</Grid>
Code-behind:
public partial class MainWindow : Window
{
public class ExampleItem
{
public int Value { get; set; }
public bool IsSelected { get; set; }
}
public class Example
{
public List<ExampleItem> Items { get; } = new List<ExampleItem>();
public Example()
{
for (var i = 0; i < 10; i++)
{
Items.Add(new ExampleItem { Value = i });
}
}
}
public List<Example> Examples { get; } = new List<Example>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
Examples.Add(new Example());
Examples.Add(new Example());
}
}

How to access checkbox in ListBox items

I'm trying to make a ListBox with checkboxes into it.
In xaml I have:
<ListBox ItemsSource="{StaticResource ResourceKey=lstMaterialesCL}" SelectionMode="Multiple" Name="lstMaterial" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="chkMaterial" Content="{Binding DescCompuesta}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and my ListBox looks as:
It is ok, but look it, when I checked "Municipales" the item in the ListBox is not selected, and when I select in the ListBox "Industriales" it is not checked
If I inspect the items selected into the ListBox it don't coincide with the items Checked
foreach (var item in lstMaterial.SelectedItems)
{
MessageBox.Show(((MaterialesCL)item).DescCompuesta);
}
It shows me "Oficiales", "Industriales" and "Destrucciones" but the user was want select "Municipales" and "Destrucciones"
How I can to make coincide the ListBox items selected with the CheckBox checked if the CheckBox checked is mandatory?
XAML:
DescCompuestaList is a list of CheckListGeneric
<ListBox ItemsSource="{Binding DescCompuestaList}" >
<ListBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding DescCompuesta}" IsChecked="{Binding IsChecked}"/>
</HierarchicalDataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here your CheckListGeneric class
public class CheckListGeneric: ViewModelBase
{
#region ..:: Fields ::..
private bool _isChecked;
#endregion
#region ..:: Properties ::..
public long Id { get; set; }
public string DescCompuesta{ get; set; }
public bool IsChecked
{
get { return _isChecked; }
set { _isChecked = value; OnPropertyChanged("IsChecked"); }
}
#endregion
}
You can get all selected using a simple query
var selectedItems = DescCompuestaList.Where(x => x.IsChecked)
simple as life.
How about binding CheckBox's IsChecked property to ListBoxItem's IsSelected property.
Something like: IsChecked={Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}
In your example:
<ListBox ItemsSource="{StaticResource ResourceKey=lstMaterialesCL}" SelectionMode="Multiple" Name="lstMaterial" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="chkMaterial" Content="{Binding DescCompuesta}" IsChecked={Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
For me it's always easiest way to use Blend for making templates.
Open project in blend and make one listBox, then select that ListBox and add template like in picture.
just to show purpose i have add simple checkBox and TextBlock, and you can make it as u like it.
in ViewModel i have made simple observable collection just to show purpose and bound ItemsSource to Users:
public class TestVM
{
public ObservableCollection<User> Users { get; set; }
public TestVM()
{
Users = new ObservableCollection<User>
{
new User{ IsChecked=true, Name="User1" },
new User{ IsChecked=false, Name="User2" },
new User{ IsChecked=true, Name="User3" },
new User{ IsChecked=false, Name="User3" },
};
}
}
public class User
{
public bool IsChecked { get; set; }
public string Name { get; set; }
}
This way you can make any template you like.
Wow, I tested all your suggestions, thank you so much to all, after all tests, the solution how I need it is more or less like this:
On xaml:
<StackPanel Orientation="Horizontal">
<ListBox ItemsSource="{Binding MaterialesVM}" SelectionMode="Multiple"
Name="ListBoxMateriales" Width="300" Height="200"
HorizontalAlignment="Left" VerticalAlignment="Top">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsChecked, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Content="{Binding DescCompuesta}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="btnPrueba" Style="{StaticResource BotonContent}"
Content="Selected" Width="80" Height="30" HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10,0,0,0" Click="btnPrueba_Click"/>
<Button Name="btnLimpia" Style="{StaticResource BotonRechazar}"
Width="30" Height="30" Click="btnLimpia_Click" HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5,0,0,0" ToolTipService.ToolTip="Limpia Todos"/>
<Button Name="btnMarca" Style="{StaticResource BotonAceptar}"
Width="30" Height="30" Click="btnMarca_Click" HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5,0,0,0" ToolTipService.ToolTip="Selecciona Todos"/>
</StackPanel>
On this way I don't care on where clicked, the item will be selected or un-selected (on the CheckBox and on the ListItem)
And to see the items selected:
// To show the selected items
private void btnPrueba_Click(object sender, RoutedEventArgs e)
{
var selectedItems = MaterialesVM.Where(x => x.IsChecked);
foreach (var item in selectedItems)
{
MessageBox.Show(((MaterialesCL)item).DescCompuesta);
}
}
To select all items:
// To select all items
private void btnMarca_Click(object sender, RoutedEventArgs e)
{
var Items = ListBoxMateriales.Items;
foreach (MaterialesCL item in Items)
{
item.IsChecked = true;
}
}
To un-select all items:
// To un-select all items
private void btnLimpia_Click(object sender, RoutedEventArgs e)
{
var Items = ListBoxMateriales.Items;
foreach (MaterialesCL item in Items)
{
item.IsChecked = false;
}
}
and my classes are:
MaterialesCL.cs
public class MaterialesCL : INotifyPropertyChanged
{
public int Material { get; set; }
public string Descripcion { get; set; }
public string DescCompuesta { get; set; }
private bool _ischecked;
public bool IsChecked
{
get { return _ischecked; }
set
{
_ischecked = value;
OnPropertyChanged("IsChecked");
}
}
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
LstMaterialesCL.cs
public class LstMaterialesCL : ObservableCollection<MaterialesCL>, INotifyPropertyChanged
{
public LstMaterialesCL()
{
BasculaEntities _context = new BasculaEntities();
var Query = (from m in _context.Materiales
select new { m.Material, m.Descripcion}
).OrderBy(m => m.Material).ToList();
foreach (var item in Query)
{
this.Add(new MaterialesCL { Material = item.Material,
Descripcion = item.Descripcion, DescCompuesta = item.Material.ToString("000") + " - " + item.Descripcion,
IsChecked=false});
}
}
}
and on my UserControl MaterialesVM is an instance of LstMaterialesCL
LstMaterialesCL MaterialesVM = new LstMaterialesCL();
and so my test of ListBox with CheckBoxes works as I need.
Thank to all you I has learn so much.

CollectionView only sorting first time (when create viewmodel again it doesnt)

I have problems to understand the CollectionView. I implemented it with some sorting/grouping. But it is only sorting the first time the view is created.
View:
<DataGrid ItemsSource="{Binding varCollectionview}" Grid.Row="2" AutoGenerateColumns="False" SelectedItem="{Binding selVariable}" Grid.ColumnSpan="2">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name.value}" Foreground="White" Background="Gray" HorizontalAlignment="Stretch" TextAlignment="Left" Padding="10,0" />
<ItemsPresenter></ItemsPresenter>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle></GroupStyle>
</DataGrid.GroupStyle>
<i:Interaction.Behaviors>
<behavior:DataGridScrollIntoViewBehavior />
</i:Interaction.Behaviors>
<DataGrid.Columns>
...
...
the MainViewModel call:
variablenVm = new VariablenViewModel(cfg);
it is called everytime i load other values etc. for reloading the Tab.
the actual ViewModel for the TabItem:
public class VariablenViewModel : ViewModelBase
{
private string _filterString;
public string filterString
{
get { return _filterString ; }
set
{
_filterString = value.ToLower();
RaisePropertyChanged("filterString ");
varCollectionview.Refresh();
}
}
private SimuVariable _selVariable;
public SimuVariable selVariable
{
get { return _selVariable; }
set
{
_selVariable = value;
RaisePropertyChanged("selVariable");
}
}
private Konfiguration _cfg;
public Konfiguration cfg
{
get { return _cfg; }
set
{
_cfg = value;
RaisePropertyChanged("cfg");
}
}
private ICollectionView _varCollectionview;
public ICollectionView varCollectionview
{
get { return _varCollectionview; }
set
{
_varCollectionview = value;
RaisePropertyChanged("varCollectionview");
}
}
public VariablenViewModel(Konfiguration _mainCfg)
{
cfg = _mainCfg;
hasSomeValueChanged = false;
_filterString = "";
_neueVariableCommand = new RelayCommand(() =>
{
...
});
_loescheVariableCommand = new RelayCommand(
...
);
varCollectionview = (CollectionView)CollectionViewSource.GetDefaultView(cfg.variablen);
if (varCollectionview.GroupDescriptions.Count == 0)
{
varCollectionview.GroupDescriptions.Add(new PropertyGroupDescription("varType"));
}
if (varCollectionview.SortDescriptions.Count == 0)
{
varCollectionview.SortDescriptions.Add(new SortDescription("varType", ListSortDirection.Descending));
varCollectionview.SortDescriptions.Add(new SortDescription("name", ListSortDirection.Ascending));
}
varCollectionview.Filter = new Predicate<object>(filter);
varCollectionview.Refresh();
}
...
}
edit: When i first look at the view, the collection is sorted. If i create a new Viewmodel after that, sorting doesnt happen.
After some Debugging i found out that the Sortdescriptions get lost. In the Constructor i add 2 Sortdescription and 1 groupdescription. Some time later i have only the groupdescription left and the sorts are gone and i don't know why :(
http://imgur.com/U7362YP
-somewhere the SortDescriptions are lost/deleted
Another question, is it possible to add a SortDescription like varType.id?
Hell yeah, i found a Solution.
varCollectionview = new CollectionViewSource();
varCollectionview.Source = cfg.variablen;
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate()
{
varCollectionview.View.SortDescriptions.Add(new SortDescription("varType", ListSortDirection.Descending));
varCollectionview.View.SortDescriptions.Add(new SortDescription("name", ListSortDirection.Ascending));
varCollectionview.View.GroupDescriptions.Add(new PropertyGroupDescription("varType"));
varCollectionview.View.Filter = new Predicate<object>(filter);
varCollectionview.View.Refresh();
}));
it seems that the View is noticed that the source is updated and deletes the sortdescriptions, but that happes delayed. All to do is add the sortdescription also delayed

Categories

Resources