WPF ListBox multiple selection not raise SelectedItem on model - c#

I have a problem with my ListBox
I've set the ListBox selection mode to Multiple
The problem is that the Selected Item fire only once, and when I select other items, it is doesn't change the SelectedItem at the view model.
I'm sure its selection them because I bind a property to true when they are selected, but the selected item does not update.
For example:
Lets say I have ListBox with the following:
A
B
C
Choose A -> the ViewModel update the selectedItem to A
Choose B -> The ViewModel doesn't update the SelectedItem but I can see that B is selected
When I deselect A, the ViewModel update the SelectedItem to null
Someone already faced that issue?
My main goal is to keep my SelectedItem update to the one I chose
He is my code for the View:
<ListBox HorizontalAlignment="Center" ItemsSource="{Binding AvailableDagrVMEs}"
SelectedItem="{Binding SelectedDagrVME}"
SelectionMode="Multiple"
VirtualizingStackPanel.IsVirtualizing="False"
ScrollViewer.HorizontalScrollBarVisibility="Auto" Padding="0" Margin="0" ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.PanningMode="HorizontalOnly"
Background="Transparent"
BorderThickness="0">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Taken, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Image Source="{Binding Icon , Converter={StaticResource BitmapToImageSourceConverter}}"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And ViewModel:
public BaseVME SelectedDagrVME
{
get { return selectedDagrVME; }
set
{
if (selectedDagrVME != value)
{
Set("SelectedDagrVME", ref selectedDagrVME, value);
}
}
}
I've tried: TwoWay binding/UpdateTriggerSource

Since you have Multiple selection, the SelectedItem will always be the first selected in your selection. To get the last selected Item you have to register for SelectionChanged event and access SelectedItems property, which contains all items from your selection. You can implement it with a behavior for you could reuse it.
Behavior:
public class ListBoxSelection : Behavior<ListBox>
{
public static readonly DependencyProperty LastSelectedProperty = DependencyProperty.Register(nameof(LastSelected), typeof(object), typeof(ListBoxSelection), new PropertyMetadata(default(object)));
public object LastSelected
{
get
{
return (object)GetValue(LastSelectedProperty);
}
set
{
SetValue(LastSelectedProperty, value);
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//you can also use whatever logic if you evaluate e.RemovedItems and e.AddedItems
if ((AssociatedObject?.SelectedItems?.Count??0)>0)
{
LastSelected = AssociatedObject.SelectedItems[AssociatedObject.SelectedItems.Count-1];
}
else
{
LastSelected = null;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
}
}
XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:b="clr-namespace:NameSpaceWhereBahaviorDefined"
<ListBox ...>
<i:Interaction.Behaviors>
<b:ListBoxSelection LastSelected = "{Binding VMSelection}" />
</i:Interaction.Behaviors>
</ListBox>

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

Which button is clicked DataTemplate WPF MVVM

my question here is how to know which button is clicked. My buttons are bound to property of type ObservableCollection which contains objects of type Item and I need to use that object in my ViewModel when a button is clicked. Any ideas how to know which button is clicked? I had few ideas, like sending multiple Command Parameters (1.SelectedItems from ListBox 2.The Object from the button) or bind the object from the button to another property of type Item in the ViewModel after the button is clicked in order to use it. Any ideas will be apreciated.
I have this DataTemplate for buttons
<DataTemplate x:Key="ButtonTemplate">
<WrapPanel>
<Button x:Name="OrderButton"
FontSize="10"
Height="80" Width="80"
Content="{Binding Name}"
Command="{Binding OrderCommand,
Source={StaticResource OrderViewModel}}"
CommandParameter="{Binding ElementName=ListBoxUserControl, Path=SelectedItems}">
</Button>
</WrapPanel>
</DataTemplate>
My ViewModel
public class OrderViewModel : ObservableCollection<Order>, INotifyPropertyChanged
{
public CreateOrderCommand CreateOrderCommand { get; set; }
public ObservableCollection<Item> Data { get; set; }
public OrderViewModel()
{
this.CreateOrderCommand = new CreateOrderCommand(this);
DataObservableCollection data= new DataObservableCollection();
Data = data;
}
}
And I populate my buttons like this
<WrapPanel x:Name="OrderButtons">
<ItemsControl ItemTemplate="{StaticResource ButtonTemplate}"
ItemsSource="{Binding Data, Source={StaticResource OrderViewModel}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</WrapPanel>
Simply change the Button.CommandParameter binding to CommandParamter="{Binding}" if you want the data context of the button (i.e. the item from your items source) as the command parameter or,
CommandParameter="{Binding RelativeSource={RelativeSource Self}}" if you want the actual button that was clicked.
First send the Button DataContext using the CommandParameter. To send the SelectedItem of your Listbox you can use
<Listbox SelectedItem="{Binding SelectedItem}"/>
in your Listbox and make a SelectedItem property in your ViewModel.
private YourItemObject mySelectedItem;
public YourItemObject SelectedItem
{
get { return mySelectedItem; }
set
{
value = mySelectedItem
}
Now you can use the SelectedItem in your ViewModel when the Button gets clicket. If you have multiple selections it gets a little bit more tricky ;).
private ButtonClicked(Parameter object)
{
SelectedItem.UsingIt();
if(object is YourButtonDataContext){
YourButtonDataContext.UsingIt();
}
}
Update with MultiSelection:
With Multiselection you have to do your own Listbox.
public class CustomListBox : ListBox
{
public CustomListBox()
{
this.SelectionChanged += CustomListBox_SelectionChanged;
}
void CustomListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList = this.SelectedItems;
}
#region SelectedItemsList
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(CustomListBox), new PropertyMetadata(null));
#endregion
}
In the ViewModel you have to have a property with the SelectedItems.
private IList mySelectedData = new List<SelectedDataObject>();
public IList SelectedData
{
get { return mySelectedData ; }
set
{
if (mySelectedData != value)
{
mySelectedData = value;
RaisePropertyChanged(() => SelectedData);
}
}
}
The XAML Looks like this:
<local:CustomListBox ItemsSource="{Binding YourList}" SelectionMode="Extended" SelectedItemsList="{Binding SelectedData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
...
</local:CustomListBox>
Source for Multiselection in DataGrid is: https://stackoverflow.com/a/22908694/3330227

How to make a datagrid see edits in a control?

I have a user control that I am using to populate a datagrid.
I would like the user to be able to add items by editing the empty row at the bottom. (This is why I am using a datagrid rather than an itemscontrol) However the datagrid does not realise that the last item is edited unless the user clicks the background of the control. I would like the new item to be added when the user makes changes on the properties that the control exposes.
XAML of the control:
<UserControl x:Class="ControlTest.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ControlTest"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="300"
DataContext="{Binding Path=., Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Path=p1, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Width="300"
Height="30"
VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding Path=DropDownValues,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=local:MyControl}}"
SelectedItem="{Binding Path=p2, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Height="30"/>
</StackPanel>
</UserControl>
cs:
public partial class MyControl : UserControl
{
private static readonly DependencyProperty DropDownValuesProperty =
DependencyProperty.Register(
"DropDownValues",
typeof(List<String>),
typeof(MyControl),
new FrameworkPropertyMetadata(new List<String>()
));
public List<String> DropDownValues
{
get
{
return (List<String>)GetValue(DropDownValuesProperty);
}
set
{
SetValue(DropDownValuesProperty, value);
}
}
public MyControl()
{
InitializeComponent();
}
}
DataGrid XAML
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding objs, Mode=TwoWay}"
HeadersVisibility="None"
Margin="0,0,0.4,0"
CanUserAddRows="True"
>
<DataGrid.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</DataGrid.ItemsPanel>
<DataGrid.Columns>
<DataGridTemplateColumn Width="300">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="local:Measure">
<local:MyControl
DataContext="{Binding ., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DropDownValues=
"{Binding DataContext.list, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
Width="300"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Can I make this work, and/or is there a better way to do this?
I would like to suggest you a different way of doing that:
set CanUserAddRows=false on your DataGrid and then manually add rows to the ObservableCollection<Something> to which your DataGrid is bound to.
OR
If you are still interested in the approach that you follow:
In your xaml file:
<DataGrid x:Name="myDataGrid" CellEditEnding="DataGrid_CellEditEnding" .....>
<!--Some Code-->
</DataGrid>
Then in the Code-Behind:
private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
myDataGrid.CommitEdit();
}
If you don't understand anything then feel free to ask.
Update
If you are following the same approach:
In your DataGrid's Beginning edit event you can try:
private void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
if ((selectedRow as DataGridRow).Item.ToString() != "{NewItemPlaceholder}")
{
//Here you can add the code to add new item. I don't know how but you should figure out a way
}
}
Note: The code mentioned above is not tested.
I would also suggest you :
Not to use DataGrid. Instead use ListBox. Because, you are trying to add some data. At this time you never need sorting, searching and column-reordering fascilities. In such scenario, ListBox is useful as it is light-weight control than datagrid. I have a sample here: https://drive.google.com/open?id=0B5WyqSALui0bTXFGZWxQUWVRdkU
Is the problem that the UI is not being notified of changes to the objs collection? What I would do is try setting up whatever view model that contains objs to make objs an observable collection. I would also ensure that whatever objs is an observable collection of implements INotifyPropertyChanged and that properties p1 and p2 both fire OnPorpertyChanged when they are set.
public ObservableCollection<YourObject> objs
and
public class YourObject : INotifyPropertyChanged
{
protected void OnPropertyChanged(string Name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(Name));
}
}
private string _p1;
public string p1
{
get { return _p1; }
set
{
if (_p1 != value)
{
_p1 = value;
OnPropertyChanged("p1");
}
}
}
private string _p2;
public string p2
{
get { return _p2; }
set
{
if (_p2 != value)
{
_p2 = value;
OnPropertyChanged("p2");
}
}
}
}

WPF Customcontrol, Templates, Inheritance and Dependencyproperties

I've got some troubles with a custom control I need to create. I try to explain you my needs first
I need to have a combobox that permits to check more than one item at time (with checkbox) but I want it to be smart enought to bind to a specific type.
I've found some MultiSelectionComboBox but none reflects my need.
Btw my main problem is that I wish to have a generic class as
public class BaseClass<T> : BaseClass
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<T>), typeof(BaseClass<T>), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(BaseClass<T>.OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
int i = 0;
//MultiSelectComboBox control = (MultiSelectComboBox)d;
//control.DisplayInControl();
}
public IEnumerable<T> ItemsSource
{
get { return (IEnumerable<T>)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
}
public class BaseClass : Control
{
}
and a more context specific item for example
public class MultiCurr : BaseClass<Currency>
{
static MultiCurr()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiCurr), new FrameworkPropertyMetadata(typeof(MultiCurr)));
}
}
In my App.xaml I've defined a resource as
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding ItemsSource}" DisplayMemberPath="Description" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
In my MainWindow I've created an object as
<Grid>
<local:MultiCurr x:Name="test" ItemsSource="{Binding Currencies}"></local:MultiCurr>
</Grid>
and the MainWindow.cs is defined as
public partial class MainWindow : Window, INotifyPropertyChanged
{
private IList currencies;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var lst = new List<Currency>();
for (int i = 0; i < 10; i++)
{
var curr = new Currency
{
ID = i,
Description = string.Format("Currency_{0}", i)
};
lst.Add(curr);
}
Currencies = lst;
}
public IList<Currency> Currencies
{
get
{
return this.currencies;
}
set
{
this.currencies = value;
NotifyPropertyChanged("Currencies");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And here's the result ...
I was wondering what am I doing wrong? is it possible what am I tring to achieve?
Thanks
UPDATE #1:
I've seen that the main problem is the datacontext of the custom usercontrol
<Application.Resources>
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding **Currencies**}" DisplayMemberPath="{Binding **DisplayMemeberPath**}" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
If I put ItemsSource as Currency (which is a property of the MainWindow) it shows.
If I put ItemsSource and DisplayMemberPath (which are defined in the BaseClass no.. how can I set the context of the usercontrol to itself?)
UPDATE #2
I've added a GoogleDrive link to the project here if anyone wants to try the solution
Thanks
Combobox is not suitable control for multiselection, because it has given behaviour, that when yo select item, Combobox closes itself. That's why Combobox doest not have SelectionMode property like ListBox. I think that ListBox inside expander is what you need.
Generic Types are not a way to go. WPF handles this different, better way. Take listbox as an example. If you bind listbox.itemssource to generic observable collection, and you try to define e.g ItemTemplate, you get full intellisense when writing bindings and warning if you bind to not existing property. http://visualstudiomagazine.com/articles/2014/03/01/~/media/ECG/visualstudiomagazine/Images/2014/03/Figure8.ashx WPF designer automatically recognizes type parameter of your observable collection. Of cousre you need to specify type of datacontext in your page by using something like this: d:DataContext="{d:DesignInstance search:AdvancedSearchPageViewModel}". However your control dont have to be and shouldn't be aware of type of items.
Following example demonstrates control that meets your requirements:
<Expander>
<Expander.Header>
<ItemsControl ItemsSource="{Binding ElementName=PART_ListBox, Path=SelectedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Mode=OneWay}" />
<Run Text=";" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Expander.Header>
<Expander.Content>
<ListBox x:Name="PART_ListBox" SelectionMode="Multiple">
<ListBox.ItemsSource>
<x:Array Type="system:String">
<system:String>ABC</system:String>
<system:String>DEF</system:String>
<system:String>GHI</system:String>
<system:String>JKL</system:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
</Expander.Content>
</Expander>
I reccomend you to create control derived from ListBox (not usercontrol).
I have hardcoded datatemplates, but you should expose them in your custom dependency properties and use TemplateBinding in you control template. Of course you need to modify expander so it looks like combobox and ListBoxItem style so it looks like CheckBox, but it is ease.

How to create attached property for ItemsControl

I have the following ItemsControl, as shown it has hard-coded values, I would like to shift these values into an attached property, probably an ObservableCollection or something similar.
How to create this attached property and how to bind it.
<ItemsControl Grid.Column="0" VerticalAlignment="Stretch" Name="ItemsSelected">
<sys:Double>30</sys:Double>
<sys:Double>70</sys:Double>
<sys:Double>120</sys:Double>
<sys:Double>170</sys:Double>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="SlateGray" Width="18" Height="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
[EDIT]
So I think I have the attached property figured:
public static class ScrollBarMarkers
{
public static readonly DependencyProperty MarkersSelectedCollectionProperty =
DependencyProperty.RegisterAttached("MarkersSelectedCollection", typeof(ObservableCollection<double>), typeof(ScrollBarMarkers), new PropertyMetadata(null));
public static ObservableCollection<double> GetMarkersSelectedCollection(DependencyObject obj)
{
return (ObservableCollection<double>)obj.GetValue(MarkersSelectedCollectionProperty);
}
public static void SetMarkersSelectedCollection(ItemsControl obj, ObservableCollection<double> value)
{
obj.SetValue(MarkersSelectedCollectionProperty, value);
}
}
What I'm wondering now is the best way to get the ItemsControl object before calling the following in the selection changed behavior:
ScrollBarMarkers.SetMarkersSelectedCollection(ItemsControl, initSelected);
The style of the customized vertical scrollbar is setup in the Window.Resources
The behavior is set up on the DataGrid like so:
<DataGrid Name="GenericDataGrid">
<i:Interaction.Behaviors>
<helpers:DataGridSelectionChanged />
</i:Interaction.Behaviors>
</DataGrid>
My selection changed behavior:
public class DataGridSelectionChanged : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += DataGrid_SelectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -= DataGrid_SelectionChanged;
}
void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ObservableCollection<double> initSelected = new ObservableCollection<double>();
initSelected.Add(30);
initSelected.Add(60);
initSelected.Add(100);
//Just trying to figure out how best to get the ItemsControl object.
ScrollBarMarkers.SetMarkersSelectedCollection(itemsControlObj, initSelected);
}
}
Below is an example of the markers in the scrollbar, a ItemsControl has been added to the custom vertical scrollbar as per the code right at the top of the question.
If I understand your question, you want bind an ObservableCollection to ItemsControl and when the items are long the scrollbar will appear.
This solution could serve you.
[I will working with MVVM]
You can create a ObservableCollection in your code.
private ObservableCollection<int> _list = new ObservableCollection<int>();
public ObservableCollection<int> List
{
get { return _list ; }
set { _list = value; RaisePropertyChanged("List"); }
}
Now, you binding Collection to ItemsControl
<ScrollViewer HorizontalAlignment="Left" Width="254" Height="Auto" >
<ItemsControl x:Name="ItemsControlComputers" ItemsSource="{Binding List, Mode=TwoWay}" Height="Auto"
HorizontalAlignment="Left" Width="254" ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
Background="{x:Null}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="254">
<TextBlock Text="{Binding }" Margin="4,0,0,5" VerticalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Went down the wrong track with this instead of creating a DependencyProperty I should have just created a plain property, however because it is UI related I did not want it with my ViewModel. So I created a class with singleton pattern in the same namespace as my behavior and other attached properties. This also means I can set the collection from any behaviors.
Here is the binding:
<ItemsControl ItemsSource="{Binding Source={x:Static helpers:MyClass.Instance}, Path=SelectedMarkers}">
Here is the class with singleton pattern
public class MyClass : INotifyPropertyChanged
{
public static ObservableCollection<double> m_selectedMarkers = new ObservableCollection<double>();
public ObservableCollection<double> SelectedMarkers
{
get
{
return m_selectedMarkers;
}
set
{
m_selectedMarkers = value;
NotifyPropertyChanged();
}
}
private static MyClass m_Instance;
public static MyClass Instance
{
get
{
if (m_Instance == null)
{
m_Instance = new MyClass();
}
return m_Instance;
}
}
private MyClass()
{
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}

Categories

Resources