How to disable unchecked checkbox in listbox wpf? - wpf

I have 30 checkbox. If i checked any 6 checkbox, remaining 24 checkbox should be disabled and if i unchecked any one checkbox from 6, all checkbox should be enabled.
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="4"
CornerRadius="5" Margin="6"
>
<CheckBox Uid="checkbox1" Name="checkbox1" Checked="CheckBox_Checked" Unchecked="CheckBox_UnChecked" IsChecked="{Binding ElementName=button,Path=IsChecked,Mode=OneWay}">
<Image
Source="{Binding Path=UriSource}"
Stretch="Fill"
Width="100" Height="120"
/>
</CheckBox>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<ListBox Name="List1" ItemsSource="{Binding}" Margin="-50,-8,93,0" RenderTransformOrigin="0.5,0.5" Height="289" VerticalAlignment="Top" >
<ListBox.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.975" ScaleY="0.997"/>
<SkewTransform AngleY="-8.98" AngleX="9.705"/>
<RotateTransform Angle="9.419"/>
<TranslateTransform Y="76.889" X="64.258"/>
</TransformGroup>
</ListBox.RenderTransform>
</ListBox>
c#:
if (iList.Count == 6) { List1.IsEnabled = false; }
it disables whole listbox.
ilist contains collection of checked checkbox values.
any method to do this?
please help me.

If you want to disable the checkboxes individually i would suggest binding to their IsEnabled property.You can iterate through your list everytime a listbox is checked or unchecked to see if 6 items are checked. If they are you can just iterate through the list again and set the now binded IsEnabled property to false for every item that isn't already checked. I guess you are working in code behind, but i would recommend doing it with a viewmodel. This would look something like this:
ViewModel
public class TestViewModel
{
private bool _disabledCheckBoxes;
public TestViewModel()
{
TestList = new ObservableCollection<CheckBoxItem>();
for (int i = 0; i < 30; i++)
{
TestList.Add(new CheckBoxItem(true));
}
}
public ObservableCollection<CheckBoxItem> TestList { get; set; }
public RelayCommand CheckBoxChanged
{
get { return new RelayCommand(OnCheckBoxChanged); }
}
private void OnCheckBoxChanged()
{
if (_disabledCheckBoxes)
{
foreach (var item in TestList)
{
item.IsEnabled = true;
}
_disabledCheckBoxes = false;
}
else
{
int i = 0;
foreach (var item in TestList)
{
if (item.IsChecked)
i++;
}
if (i >= 6)
{
foreach (var item in TestList)
{
if (!item.IsChecked)
item.IsEnabled = false;
}
_disabledCheckBoxes = true;
}
}
}
}
CheckBoxItem
public class CheckBoxItem : ObservableObject
{
public CheckBoxItem(bool isEnabled)
{
IsEnabled = isEnabled;
}
private bool _isEnabled;
public bool IsEnabled
{
get
{
return _isEnabled;
}
set
{
_isEnabled = value;
RaisePropertyChanged("IsEnabled");
}
}
private bool _isChecked;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
RaisePropertyChanged("IsChecked");
}
}
}
Xaml Code / View
<Window x:Class="WpfAppTests.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:WpfAppTests"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
xmlns:modelNoMvvmLight="clr-namespace:WpfAppTests"
xmlns:modelMvvmLight="clr-namespace:WpfAppTests.ViewModel"
Loaded="Loaded_Window"
Title="MainWindow" Height="350" Width="525" >
<Window.DataContext>
<modelMvvmLight:TestViewModel/>
</Window.DataContext>
<StackPanel>
<ListBox ItemsSource="{Binding TestList}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" IsEnabled="{Binding IsEnabled}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CheckBoxChanged}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CheckBoxChanged}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
I bound the Checked and UnChecked event with the command CheckBoxChanged(in the View Model) so every time the state of a checkbox changes, your requirement (checked items == 6) can be verified.
I used MvvmLight in my example, but if you consider using the mvvm pattern and don't want to use a framework you can implement ObservableObject and RelayCommand yourself.

Related

WPF MVVM ListBox MultiSelect

I Have created a list box containing list of items and i need to bind them on selection
changed(Select and deselect).
ABCD.xalm
<ListBox Grid.Column="2" Grid.ColumnSpan="9" Height="30" Margin="0 0 5 0" Foreground="{StaticResource AcresTheme}" SelectedItem="{Binding Path=UpdateSimulationItem,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding SmulationTypes, NotifyOnSourceUpdated=True}"
Background="{Binding }"
MinHeight="65" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Foreground="{StaticResource AcresTheme}"
Content="{Binding Item}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ABCD.cs (View Model)
public List<string> SimulationTypesList { get; set; } = new List<string>();
private ObservableCollection<SimulationType> _simulationTypes = new ObservableCollection<SimulationType>();
public ObservableCollection<Items> SimulationTypes
{
get
{
return _simulationTypes;
}
set
{
_simulationTypes = value;
OnPropertyChanged("SimulationTypes");
}
}
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Logic for getting the selected item
_updateSimulationItem = value;
OnPropertyChanged("UpdateSimulationItem");
}
}
public ABCD()
{
SimulationTypes.Add(new SimulationType() { Item = "Simulation 1", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 2", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 3", IsSelected = false });
}
Items.cs
public class Items: ViewModelBase
{
private string item;
public string Item
{
get { return item; }
set
{
item = value;
this.OnPropertyChanged("Item");
}
}
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
I did try the solution given in https://stackoverflow.com/a/34632944/12020323 This worked fine
for deleting a single item or selecting a single item.
When we select the second item it does not trigger the property change.
Error somewhere not in this code.
You may be confused about VM instances.
This is often the case for beginners.
There may be something wrong with the implementation of SimulationType. You didn't show it.
Here's a complete example of your code demonstrating that multiselect binding works correctly.
using Simplified;
using System;
using System.Collections.ObjectModel;
namespace Core2022.SO.ChaithanyaS
{
public class Item : ViewModelBase
{
private string _title = string.Empty;
public string Title
{
get => _title;
set => Set(ref _title, value ?? string.Empty);
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => Set(ref _isSelected, value);
}
}
public class SimulationType : Item
{
private int _count;
public int Count { get => _count; set => Set(ref _count, value); }
}
public class ItemsViewModel : ViewModelBase
{
private static readonly Random random = new Random();
private Item? _selectedItem;
public ObservableCollection<Item> Items { get; } =
new ObservableCollection<Item>()
{
new Item() {Title = "First" },
new SimulationType() { Title = "Yield Simulation", Count = random.Next(5, 15) },
new Item() {Title = "Second" },
new SimulationType() { Title = "HLR Simulation", Count = random.Next(5, 15) },
new SimulationType() { Title = "UnCorr HLR Simulation", Count = random.Next(5, 15)},
new Item() {Title = "Third" }
};
public Item? SelectedItem { get => _selectedItem; set => Set(ref _selectedItem, value); }
}
}
<Window x:Class="Core2022.SO.ChaithanyaS.ItemsWindow"
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:Core2022.SO.ChaithanyaS"
mc:Ignorable="d"
Title="ItemsWindow" Height="450" Width="800">
<Window.DataContext>
<local:ItemsViewModel/>
</Window.DataContext>
<UniformGrid Columns="2">
<ListBox Margin="10"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<FrameworkElement.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<CheckBox Foreground="Red"
Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<CheckBox Foreground="Green"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</CheckBox>
</DataTemplate>
</FrameworkElement.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ItemsControl ItemsSource="{Binding Items}" Margin="10"
BorderBrush="Green" BorderThickness="1"
Padding="10">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<TextBlock Foreground="Red"
Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<TextBlock Foreground="Green">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataTemplate.Resources>
<Label x:Name="cc" Content="{Binding}" Margin="1" BorderBrush="Gray" BorderThickness="1"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="cc" Property="Background" Value="LightPink"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UniformGrid>
</Window>
Perhaps you mean that in multi-selection mode, the SelectedItem property always has only the item that was selected first, until it is deselected?
Unfortunately, the implementation of such a task is not easy.
It's better to make a custom AP property and then use it in XAML.
public static class MuliSelectorHelper
{
/// <summary>Returns the value of the IsSelectedItemLast attached property for <paramref name="multiSelector"/>.</summary>
/// <param name="multiSelector"><see cref="DependencyObject"/> whose property value will be returned.</param>
/// <returns><see cref="bool"/> property value.</returns>
public static bool GetIsSelectedItemLast(DependencyObject multiSelector)
{
return (bool)multiSelector.GetValue(IsSelectedItemLastProperty);
}
/// <summary>Sets the value of the IsSelectedItemLast attached property for <paramref name="multiSelector"/>.</summary>
/// <param name="multiSelector"><see cref="MultiSelector"/> whose property value will be returned.</param>
/// <param name="value"><see cref="bool"/> value for property.</param>
public static void SetIsSelectedItemLast(DependencyObject multiSelector, bool value)
{
multiSelector.SetValue(IsSelectedItemLastProperty, value);
}
/// <summary><see cref="DependencyProperty"/> for methods <see cref="GetIsSelectedItemLast(MultiSelector)"/>
/// and <see cref="SetIsSelectedItemLast(MultiSelector, bool)"/>.</summary>
public static readonly DependencyProperty IsSelectedItemLastProperty =
DependencyProperty.RegisterAttached(
nameof(GetIsSelectedItemLast).Substring(3),
typeof(bool),
typeof(MuliSelectorHelper),
new PropertyMetadata(false, OnIsSelectedItemLastChanged));
private static void OnIsSelectedItemLastChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not Selector selector || selector.GetValue(ListBox.SelectedItemsProperty) is not IList list)
{
throw new NotImplementedException("Implemented only types that derive from Selector and that use the ListBox.SelectedItems dependency property.");
}
if (Equals(e.NewValue, true))
{
selector.SelectionChanged += OnSelectionChanged;
}
else
{
selector.SelectionChanged -= OnSelectionChanged;
}
}
private static async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 0)
return;
Selector selector = (Selector)sender;
IList selectedItems = (IList)selector.GetValue(ListBox.SelectedItemsProperty);
if (selectedItems.Count != e.AddedItems.Count && !Equals(selectedItems[0], e.AddedItems[0]))
{
selector.SelectionChanged -= OnSelectionChanged;
int beginIndex = selectedItems.Count - e.AddedItems.Count;
var selectedItemsArray = new object[selectedItems.Count];
selectedItems.CopyTo(selectedItemsArray, 0);
selectedItems.Clear();
await selector.Dispatcher.BeginInvoke(() =>
{
for (int i = selectedItemsArray.Length-1; i >= beginIndex; i--)
{
selectedItems.Add(selectedItemsArray[i]);
}
for (int i = 0; i < beginIndex; i++)
{
selectedItems.Add(selectedItemsArray[i]);
}
});
selector.SelectionChanged += OnSelectionChanged;
}
}
}
<UniformGrid Columns="2">
<ListBox Margin="10"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple"
local:MuliSelectorHelper.IsSelectedItemLast="true">
<FrameworkElement.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<CheckBox Foreground="Red"
Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<CheckBox Foreground="Green"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</CheckBox>
</DataTemplate>
</FrameworkElement.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ContentControl Content="{Binding SelectedItem}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<TextBlock Foreground="Red"
Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<TextBlock Foreground="Green">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataTemplate.Resources>
<Label x:Name="cc" Content="{Binding}" Margin="1" BorderBrush="Gray" BorderThickness="1"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="cc" Property="Background" Value="LightPink"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</UniformGrid>
Thank you #EldHasp, for the time taken to respond to my question. Very greatfull to the solution provided.
Currently I am intrested in having the complete code in ViewModel, I found the mistake that I had done in the ViewModel.
Old Code:
private ObservableCollection<SimulationType> _simulationTypes = new ObservableCollection<SimulationType>();
public ObservableCollection<Items> SimulationTypes
{
get
{
return _simulationTypes;
}
set
{
_simulationTypes = value;
OnPropertyChanged("SimulationTypes");
}
}
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Logic for getting the selected item
_updateSimulationItem = value;
OnPropertyChanged("UpdateSimulationItem");
}
}
_updateSimulationItem = value; Binds the first selected item to UpdateSimulationItem and propertychange will trigger only when that perticular item is changed.
For example:
SimulationTypes.Add(new SimulationType() { Item = "Simulation 1", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 2", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 3", IsSelected = false });
In this three items, if I select Simulation 1 then the UpdateSimulationItem will bind to Simulation 1 and the propertychange will narrow down to one item i.e. Simulation 1. Now if we click on Simulation 2 the peopertychange will not trigger as the UpdateSimulationItem is bound to only Simulation 1 item changes.
The change that I Made.
Updated code:
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Removed unnecessary code and the assignment of value to _updateSimulationItem
OnPropertyChanged("UpdateSimulationItem");
}
}
As we have binded the SimulationTypes to ItemSource in the ABC.XAML as shown below
<ListBox Foreground="{StaticResource AcresTheme}"
SelectedItem="{Binding Path=UpdateSimulationItem,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding SimulationTypes, NotifyOnSourceUpdated=True}"
MinHeight="65" SelectionMode="Multiple">
when i click on the checkbox that is present in the view, it will automatically updat the SimulationTypes as i have bound the checkbox to IsSelected.
<CheckBox Foreground="{StaticResource AcresTheme}"
Content="{Binding Item}"
IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
#EldHasp the code changes that we have to do in your code is to remove the assignment to _selectedItem in the setter property and just keep the OnPropertychange(nameOf(SelectedItem)).
public Item? SelectedItem { get => _selectedItem; set => Set(ref _selectedItem, value); }
The bold text was making the SelectedItem to bind to one item, which was restricting the trigger when other item was selected.
Once Again Thank you #EldHasp for taking out your time on this.

ListView Popup key functionality

I have implemented a listview that appears as a popup list. Now I would like to add key functionalities to it, like if whenever up arrow is pressed in a text box it should select an item in my list view and if pressing of KEY_UP/DOWN is continued it should continue changing its index respectively.
This is the EditMessageTextBox and associated EditMessageTagPopup
This is the XAML code used:
<Grid x:Name="EditGrid"
Grid.Row="1"
Visibility="{Binding EditMessageControlVisibility}"
FocusManager.IsFocusScope="False"
VerticalAlignment="Center"
Grid.Column="1"
HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="EditMessageBorder"
Grid.Row="0"
BorderThickness="1"
CornerRadius="1"
Margin="0,10,0,0"
BorderBrush="Gray">
<Grid>
<TextBlock FontSize="16"
Margin="10,0,0,3"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="Edit message"
Foreground="{StaticResource brushWatermarkForeground}"
Visibility="{Binding ElementName=EditMessageTextBox, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBox Name="EditMessageTextBox"
Text="{Binding MessageToEdit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
BorderBrush="Transparent"
BorderThickness="0"
Foreground="Black"
FontSize="16"
Margin="8,1,1,1"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"
MinHeight="35"
ScrollViewer.VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
AcceptsReturn="False"
KeyUp="OnEditMessage_KeyUp"
SpellCheck.IsEnabled="true" />
</Grid>
</Border>
<StackPanel Grid.Row="1"
Margin="0,10"
Orientation="Horizontal">
<Button Background="Transparent"
VerticalContentAlignment="Center"
Padding="5,2,5,3"
Foreground="Black"
BorderBrush="Gray"
BorderThickness="0.8"
Width="100"
materialDesign:ShadowAssist.ShadowDepth="Depth0"
Click="EditMessageCancelButton_Clicked">Cancel</Button>
<Button Name="EditMessageButton"
VerticalContentAlignment="Center"
Padding="5,2,5,3"
Background="#007a5a"
Foreground="White"
BorderBrush="#007a5a"
Margin="15,0,0,0"
materialDesign:ShadowAssist.ShadowDepth="Depth0"
BorderThickness="0.8"
IsEnabled="True"
Width="140"
Content="Save Changes"
Click="EditMessageSaveButton_Clicked" />
</StackPanel>
<Popup x:Name="EditMessageTagPopup"
AllowsTransparency="True"
IsOpen="{Binding IsOpenTagPopUp}"
StaysOpen="False"
Placement="Top"
PlacementTarget="{Binding ElementName=EditMessageTextBox}">
<Border materialDesign:ShadowAssist.ShadowDepth="Depth5"
CornerRadius="5"
Background="White"
BorderBrush="Black"
BorderThickness="0.8"
MaxHeight="200">
<ListView x:Name="EditTaggedUsers"
Focusable="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource UserListForTag}}"
SelectionChanged="EditMessageTagList_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border Name="_Border"
Padding="8">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="_Border"
Property="Background"
Value="#FF3BD38E" />
<Setter Property="Foreground"
Value="White" />
</Trigger>
<Trigger Property="IsSelected"
Value="True">
<Setter TargetName="_Border"
Property="Background"
Value="#FF205B4B" />
<Setter Property="Foreground"
Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="-15,0,0,0"
Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0"
RadiusY="5"
RadiusX="5"
Height="20"
Width="20">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding ProfileImage}"
Stretch="UniformToFill" />
</Rectangle.Fill>
</Rectangle>
<TextBlock Grid.Column="1"
Text="{Binding FullName}"
Margin="-10,0,0,0" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</Popup>
</Grid>
and here is code behind:
ApplicationContext.StoredEditingMessage = (String)ApplicationContext.EditMessageText;
var messageData = ((TextBox)sender).DataContext as ChatsModel;
var EditMessagePopup = FindEditMessagePopup(MessageList);
Border EditEessageBorder = EditMessagePopup.Child as Border;
ListView EditMessageTagList = EditEessageBorder.Child as ListView;
Dispatcher?.Invoke(() =>
{
if (_contactsViewModel.GroupedChatByDate
.Find(x => messageData != null && x.MessageGuid == messageData.MessageGuid)
.IsOpenTagPopUp == false) return;
var index = _contactsViewModel.UsersListForTag.IndexOf(_contactsViewModel.UsersListForTag.FirstOrDefault(x => x.Selected == true));
switch (e.Key)
{
case Key.Up:
if (EditMessageTagList.SelectedIndex > 0)
{
EditMessageTagList.SelectedIndex -= 1;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
else
{
EditMessageTagList.SelectedIndex = _contactsViewModel.UsersListForTag.Count - 1;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
break;
case Key.Down:
if (EditMessageTagList.SelectedIndex + 1 == _contactsViewModel.UsersListForTag.Count)
{
EditMessageTagList.SelectedIndex = 0;
_contactsViewModel.UsersListForTag[index].Selected = true;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
else
{
EditMessageTagList.SelectedIndex += 1;
_contactsViewModel.UsersListForTag[index].Selected = true;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
break;
}
_contactsViewModel.UsersListForTag.ForEach(x => x.Selected = false);
if (index != -1)
{
_contactsViewModel.UsersListForTag[index].Selected = true;
}
});
I have tried adding an item in scroll into view() instead of selected index yet no update
when there is a perfect selection made this function is invoked from code behind
private void EditMessageTagList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
var messageModel = ((ListView)sender).DataContext as ChatsModel;
if (((ListView)sender).SelectedItem is UserModel selectedUserForTag)
{
// _contactsViewModel.GroupedChatByDate.Find(x => messageModel != null && x.MessageGuid == messageModel.MessageGuid) .IsOpenTagPopUp = false;
string SelectedTag = (selectedUserForTag.Id == ApplicationContext.CurrentLoggedInUserGuid) ? $"{selectedUserForTag.UserName.Replace("(you) ", "")} " : $"{selectedUserForTag.UserName} ";
_contactsViewModel.GroupedChatByDate.Find
(x => messageModel != null && x.MessageGuid == messageModel.MessageGuid)
.MessageToEdit = "#" + SelectedTag;
}
// ((ListView) sender).SelectedItem = null;
}
catch (Exception exception)
{
LoggingManager.Error(exception);
}
}
Here is screen recording regarding issue
and
Here is working functionality
The problem is that after each navigation to the next item of the ListView you want to set focus to the selection TextBox which binds to the SelectedItem. Otherwise navigating the items of a ListView with the help of the arrow keys is already the default behavior of the ListView.
The simplest solution is to capture the keyboard input using UIElement.InputBinding on the selection TextBox (which enables to handle the keys in the view model) and then
Select the next/previous item
Scroll the SelectedItem into view
Move the focus to the selection TextBox
Move the caret of the selection TextBox to the end
DataItem.cs
class DataItem
{
public string FullName { get; set; }
public DataItem(string fullName) => this FullName = fullName;
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<DataItem> DataItems { get; set; }
public ICommand SelectNextCommand => new AsyncRelayCommand(SelectNextItem);
public ICommand SelectPreviousCommand => new AsyncRelayCommand(SelectPreviousItem);
private bool IsSelectedItemChangeInternal { get; set; }
private DataItem selectedDataItem;
public DataItem SelectedDataItem
{
get => this.selectedDataItem;
set
{
this.selectedDataItem = value;
OnPropertyChanged();
// Do not filter the list when the selected item was set by the user
// e.g. by using arrow keys
if (!this.IsSelectedItemChangeInternal)
{
UpdateSearchFilter();
}
}
}
private string filterKey;
public string FilterKey
{
get => this.filterKey;
set
{
this.filterKey = value;
OnPropertyChanged();
// Only apply filters when the FilterKey was changed by the user
// e.g. by editing the edit TextBox that binds to this property
if (!this.IsSelectedItemChangeInternal)
{
ApplySearchFilter();
}
}
}
public ViewModel()
{
this.DataItems = new ObservableCollection<DataItems>();
for (var index = 0; index < 100; index++)
{
this.DataItems.Add(new DataItem("name " + index.ToString());
}
}
private void ApplySearchFilter()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Games);
this.IsSelectedItemChangeInternal = true;
collectionView.Filter = item =>
string.IsNullOrWhiteSpace(this.FilterKey) || (item as DetailItem).FullName.StartsWith(this.FilterKey);
// pre-select the first match
collectionView.MoveCurrentToFirst();
this.IsSelectedItemChangeInternal = false;
}
private void UpdateSearchFilter()
{
this.IsSelectedItemChangeInternal = true;
this.FilterKey = this.SelectedDataItem.FullName;
this.IsSelectedItemChangeInternal = false;
}
private void SelectNextItem()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.DataItems);
collectionView.MoveCurrentToNext();
// Loop
if (collectionView.IsCurrentAfterLast)
{
collectionView.MoveCurrentToFirst();
}
}
private void SelectPreviousItem()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.DataItems);
collectionView.MoveCurrentToPrevious();
// Loop
if (collectionView.IsCurrentBeforeFirst)
{
collectionView.MoveCurrentToLast();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName);
}
}
MainWindow.xaml.cs
private void AdjustFocus_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
listView.ScrollIntoView(listView.SelectedItem);
Application.Current.Dispatcher.InvokeAsync(() =>
{
Keyboard.Focus(this.EditMessageTextBox);
this.EditMessageTextBox.CaretIndex = this.EditMessageTextBox.Text.Length;
});
}
private void AdjustFocus_OnOpened(object sender, EventArgs e)
{
this.EditTaggedUsers.Focus();
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContex>
<Grid>
<TextBox x:Name="EditMessageTextBox"
Text="{Binding FilterKey}">
<TextBox.InputBindings>
<KeyBinding Key="Down"
Command="{Binding SelectNextCommand}" />
<KeyBinding Key="Up"
Command="{Binding SelectPreviousCommand}" />
</TextBox.InputBindings>
</TextBox>
<Popup IsOpen="True"
Opened="AdjustFocus_OnOpened"
StaysOpen="False"
Placement="Top"
PlacementTarget="{Binding ElementName=EditMessageTextBox}">
<ListView IsSynchronizedWithCurrentItem="True"
Height="400"
SelectedItem="{Binding SelectedDataItem}"
ItemsSource="{Binding DataItems}"
SelectionChanged="AdjustFocus_OnSelectionChanged">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type DataItem}">
<TextBox Text="{Binding FullName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Popup>
</Grid>
</Window>
Remarks
As the navigation is done using the CollectionView of the ItemsSource the ListView.IsSynchronizedWithCurrentItem property must be set to true. Otherwise the navigation of the CollectionView won't effect the view.

prism 5.0 InvokeCommandAction SelectionMode="Multiple"

I want to detect all selected items in List Box through InvokeCommandAction prism 5.0.
XAML:
<Window x:Class="Selection.Prism5._0.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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:Selection.Prism5"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<FrameworkElement.DataContext>
<local:MainViewModel />
</FrameworkElement.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectItemsCommand}"
TriggerParameterPath="AddedItems" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
View Model:
public class MainViewModel : BindableBase
{
public MainViewModel()
{
this.Items = new List<Model>
{
new Model {Id=1,Name="Name 1" },
new Model {Id=2,Name="Name 2" },
new Model {Id=3,Name="Name 3" },
new Model {Id=4,Name="Name 4" },
new Model {Id=5,Name="Name 5" },
new Model {Id=6,Name="Name 6" }
};
SelectItemsCommand = new DelegateCommand<object[]>((items) =>
{
if (items != null && items.Count() > 0)
{
SelectedItems = items.Select(i => (Model)i);
}
});
}
public ICommand SelectItemsCommand { get; private set; }
private IEnumerable<Model> _items;
public IEnumerable<Model> Items
{
get { return _items; }
set
{
_items = value; base.OnPropertyChanged("Items");
}
}
private IEnumerable<Model> _selectedItems;
public IEnumerable<Model> SelectedItems
{
get { return _selectedItems; }
set
{
_selectedItems = value; base.OnPropertyChanged("SelectedItems");
}
}
private Model _selectedItem;
public Model SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value; base.OnPropertyChanged("SelectedItem");
}
}
}
It should work, however it doesn't. When I select more than one item in GUI:
View model code recognizes only one item:
I saw this example in "What's New in Prism 5.0" by Brian Lagunas and as far as I understood, this technic may be used for multi-selection too.
What have I done wrong here?
SelectionChangedEventArgs.AddedItems gives you the selection list of a particular action. if ListBox SelectionMode is "Multiple" or "Single" you cannot select multiple items at a single click. if ListBox SelectionMode is "Extended" you can select multiple items with the help of the shift key.
SelectionChangedEventArgs.AddedItems will not give all selected items of the list box for a particular action.
For your need,
In View,
Change TriggerParameterPath="AddedItems" to TriggerParameterPath="Source.SelectedItems" .
In ViewModel
Change DelegateCommand<object[]> to DelegateCommand<System.Collections.ICollection>
Ref:
http://frststart.blogspot.com/2016/10/selectionchangedselecteditemstutorial.html
You are selecting one item at a time and the command is invoked for each time you select an item.
This is the expected behaviour.
If you want to keep track of the currently selected items you could add an IsSelected property to the Model class and use an ItemContainerStyle to bind this one to the IsSelected property of the ListBoxItem container:
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectItemsCommand}"
TriggerParameterPath="AddedItems" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectItemsCommand = new DelegateCommand<System.Collections.IList>((items) =>
{
SelectedItems = Items.Where(x => x.IsSelected).ToList();
});

Binding to a CollectionViewSource with expander style

I have a CollectionViewSource bound to an ObservableCollection of my ViewModels. I then have a style on the GroupItem which has an expander in it. I want to be able to have a 'collapse all' and an 'expand all' on the group headers, but am having trouble binding to the isExpanded on the expander in the ControlTemplate. I have this code:
<Style x:Key="GroupStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Path=**????**}" Template="{StaticResource DAExpander}">
<Expander.Header>
<DockPanel VerticalAlignment="Center">
<Image DockPanel.Dock="Left" x:Name="scItemIcon" Source="./groupIcon.ico" Height="18" Width="18"/>
<TextBlock DockPanel.Dock="Left" Text="{Binding Name}" VerticalAlignment="Center" Margin="4,0,6,0" />
</DockPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have an IsExpanded property on my view model but i cant figure out how to access it. I have found examples such as:
<Expander IsExpanded="{Binding Path=Items[0].IsExpanded}"
<Expander IsExpanded="{Binding Path=Name.IsExpanded}"
None of which seem to work. Is there a way to accomplish this or do i need to take a different approach?
Edit: more xaml, and view model code:
<ListBox ItemsSource="{Binding Source={StaticResource MyCVS}}" ItemTemplate="{StaticResource ItmesTemplate}" ItemContainerStyle="{StaticResource ItemChildStyle}" SelectedItem="{Binding SelectedItem}" >
<ListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupStyle}" />
<GroupStyle ContainerStyle="{StaticResource Group2Style}" />
</ListBox.GroupStyle>
</ListBox>
public class ItemViewModel : ViewModelBase
{
public string GroupName { get; set; }
public string SubGroupName{ get; set; }
public string ItemName { get; set; }
public bool IsExpanded {get; set;}
}
I solved this without binding by adding an event to the click on my 'Expand all' button
private void expandAll_Click(object sender, EventArgs e)
{
// get each listBoxItem by index from the listBox
for (int i = 0; i < MyListBox.Items.Count; i++)
{
ListBoxItem item = (ListBoxItem)MyListBox.ItemContainerGenerator.ContainerFromIndex(i);
// find its parents expander
var exp = FindParent<Expander>(item);
if (exp != null)
{
//if its not null expand it and see if it has a parent expander
exp.IsExpanded = true;
exp = FindParent<Expander>(exp);
if (exp != null)
exp.IsExpanded = true;
}
}
}
The FindParent function is a helper that walks up the visual tree looking for the control specified starting at the control you pass in.

Disable button while processing request

I am new to wpf and xaml (Windows development in general) and my background is asp.net and prior to that classic ASP.
I'm working on an application that needs to have the button disabled/grayed out while the processing occurs and read a post on here to do the following but it doesn't appear to be working. What am I missing?
<Window x:Class="SCGen.Application.LoadForecast.EngineExecution"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:igEditors="http://infragistics.com/Editors"
SizeToContent="WidthAndHeight"
Title="Engine Execution"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Background="{StaticResource {x:Static SystemColors.ControlBrushKey}}">
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="myStyle" BasedOn="{StaticResource ButtonStyle}">
<Setter Property="Command" Value="{Binding ExecuteEngine}" />
<Setter Property="Content" Value="Execute Engine" />
<Style.Triggers>
<Trigger Property="Command" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Border Padding="8">
<StackPanel>
<StackPanel MaxWidth="200" HorizontalAlignment="Left">
<TextBlock Text="Select Forecast Engine" TextAlignment="Center" FontSize="13" />
<igEditors:XamComboEditor ItemsSource="{Binding ForecastEngines}" SelectedItem="{Binding SelectedEngine}" Margin="0,5" />
<Button Style="{StaticResource ResourceKey=myStyle}" />
</StackPanel>
<TextBlock Text="{Binding EngineStatus}" FontSize="15" FontStyle="Italic" Margin="0,14" Width="400" TextWrapping="Wrap" />
</StackPanel>
</Border>
</Window>
I've changed the xaml to the following:
<Button Content="Execute Weather Import" Command="{Binding ExecuteWeather}" Style="{StaticResource ButtonStyle}" IsEnabled="{Binding IsEnabled}"/>
In the ViewModel I have the following:
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; }
}
and I set the _isEnabled here:
private string LaunchWeatherImport(string strVendor)
{
_isEnabled = false;
string uri = ConfigurationManager.AppSettings["ManualExecutionFacilitatorService"];
ClientConnectionInfo connection = new ClientConnectionInfo(uri) { UseSecurity = true };
connection.SetTimeouts();
Logger.LogInfo("Calling Facilitator service to manually import " + strVendor + " weather data.");
((NetTcpBinding)connection.Binding).Security.Mode = System.ServiceModel.SecurityMode.None;
using (var client = new FacilitatorManualExecutionClient(connection))
{
client.InnerChannel.OperationTimeout = TimeSpan.FromMinutes(int.Parse(ConfigurationManager.AppSettings["OperationTimeOutMinutes"]));
try
{
_isEnabled = true;
return "success";
// uncomment this line before commit
//return client.ExecuteWeather(strVendor);
}
#region catch
catch (Exception ex)
{
Logger.LogError(ex.Message, ex);
return ex.Message;
}
#endregion
}
}
I still can't get it to work properly.
For starters, you're setting the trigger on the Command property but you don't have a binding set on that property for your button:
<Button Style="{StaticResource ResourceKey=myStyle}" />
Should be:
<Button Style="{StaticResource ResourceKey=myStyle}" Command="{Binding MyCommand}" />
[Where MyCommand is the name of your actual command that you're binding to]
I am not so sure that it will work anyway though because your trigger is set to fire when the Command property is null, but if you bind to the command property, following the MVVM pattern then your command property shouldn't be null so the trigger won't fire then either.
UPDATE:
You need to implemented the INotifyPropertyChanged interface on your class that has the property.
public class MyClass : System.ComponentModel.INotifyPropertyChanged
Then add the implementation:
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
Then change your property to be:
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
NotifyPropertyChanged("IsEnabled");
}
}

Resources