How to disable deselection of items in ListView? - wpf

I've got
<ListView SelectionMode="Single" SelectedIndex="0" ItemsSource="{Binding AccountViewModels}" SelectedItem="{Binding SelectedItem}" Style="{StaticResource AccountsList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<LocalViews:AccountView Margin="{StaticResource ControlMargin}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
Is there a way to disable deselection of an item from the ListView (i.e. ctrl+click)? In other words, I don't want the user to be able to de-select an item, but of course it's OK to select another item.

Since this functionality is purely view/control related it should not be implemented in the view model but you could handle the PreviewMouseLeftButtonDown event of the ListBoxItem container like this:
<ListView SelectionMode="Single" SelectedIndex="0" ItemsSource="{Binding AccountViewModels}" SelectedItem="{Binding SelectedItem}" Style="{StaticResource AccountsList}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ItemPreviewMouseLeftButtonDown" />
</Style>
</ListBox.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<LocalViews:AccountView Margin="{StaticResource ControlMargin}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
private void ItemPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ListBoxItem lbi = sender as ListBoxItem;
e.Handled = lbi.IsSelected;
}

One way to handle this would be to use binding and add logic to disallow de-selection in the view model.
From this answer, change the IsSelected as follows:
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
if (value && !isSelected)
{
isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}

Related

Check The First Generated RadioButton in An ItemsControl Group

I want to check the first radiobutton of my list after the loading of my view.
Code
<ItemsControl HorizontalAlignment="Stretch"
ItemsSource="{Binding ListEquipement}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Checked="RadioButton_Checked"
GroupName="Equipement"
Background="{Binding Background}"
Style="{StaticResource selectedButton}"
Command="{Binding DataContext.SelectEquipementCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}"
CommandParameter="{Binding}">
<Binding Path="Text" />
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Does the data have a Boolean property which can be used to initiate the IsChecked status? If so I would bind IsChecked to that property.
Then in the ViewModel, set the first one to be true such as
ListEquipment[0].MyIsCheckedProperty = true;
On the RadioButton add a handler to the Loaded event:
<DataTemplate>
<RadioButton Content="{Binding OpName}"
GroupName="Selectables"
Loaded="RBLoaded"/>
</DataTemplate>
Then in the code behind, create a property which is a boolean status which when the status suggests this is the first time, check the radiobox:
public bool HasBeenChecked { get; set; }
private void RBLoaded(object sender, RoutedEventArgs e)
{
if (HasBeenChecked == false)
{
(sender as RadioButton).IsChecked = true;
HasBeenChecked = true;
}
}

Checkbox with DataGrid WPF

I am trying to create a DataGrid in WPF 4.0 using MVVM...
Features required -
Muti - Select rows using a checkbox (single click)
A select all checkbox to check all the checkboxes in the datagrid
Something like this -
It has been 2 days and i am not able to figure out how to solve the problem effectively..
A working example is what I need now ASAP..
I'll highly appreciate if someone has a working solution to share with me...
N please don't tell me to google this thing because none of the things worked out for me...
UPDATE -
I am using AutoGeneration of Columns
I don't want to add "IsSelected" or any of such property in my MODEL..
I am just facing 2 problems -
Firstly, "Select all" Feature i.e. checking all checkboxes on the checkbox click of the one present in the column header...(I am able to select and unselect the datagrid but not able to tick/untick the checkboxes)
Secondly, Multiple selection on mouse click without holding Ctrl Key..
When you're working with MVVM, you have to be aware of what is considered data and what is strictly UI.
Is your SelectedItems going to be part of your data, or only your UI?
If it's part of your data, you really should have an IsSelected property on your data model, even if that means extending the data class to include an IsSelected property, or creating a wrapper class that only contains bool IsSelected and object MyDataItem. The first option is probably preferred, since you could keep AutoGenerateColumns="True", and it makes the column bindings simpler.
Then you would just bind your DataGridRow.SelectedItem to the IsSelected property of the data item:
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
But if your SelectedItems is only for the UI, or if you are breaking the MVVM pattern for some reason in this instance, than you can create the unbound CheckBox and use some code behind to ensure the CheckBox is correctly synchronized to the SelectedItem.
I did a quick sample app, and here is what my code looked like:
First off, I just added the unbound CheckBox column to the column list using a DataGridTemplateColumn. This will get added before the AutoGenerateColumns list of columns.
<DataGrid x:Name="TestDataGrid" ItemsSource="{Binding Test}"
SelectionMode="Extended" CanUserAddRows="False"
PreviewMouseLeftButtonDown="TestDataGrid_PreviewMouseLeftButtonDown_1">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="TestCheckBox"
PreviewMouseLeftButtonDown="CheckBox_PreviewMouseLeftButtonDown" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Second, I added a PreviewMouseDown event to the CheckBox to make it set the IsSelected property of the row.
private void CheckBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var chk = (CheckBox)sender;
var row = VisualTreeHelpers.FindAncestor<DataGridRow>(chk);
var newValue = !chk.IsChecked.GetValueOrDefault();
row.IsSelected = newValue;
chk.IsChecked = newValue;
// Mark event as handled so that the default
// DataGridPreviewMouseDown doesn't handle the event
e.Handled = true;
}
It needs to navigate the VisualTree to find the DataGridRow associated with the clicked CheckBox to select it, and to make life easier I am using some custom VisualTreeHelpers that I have on my blog to find the DataGridRow. You can use the same code, or you can create your own method for searching the VisualTree.
And last of all, if the user clicks on anywhere other than the CheckBox, we want to disable the default DataGrid selection event. This ensures that the IsSelected value will only change when you click on the CheckBox.
There are multiple ways of doing this that will disable the selection at different levels, but to make life simple I just disabled the DataGrid.PreviewMouseLeftButtonDown event if the user didn't click on the CheckBox.
private void TestDataGrid_PreviewMouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
{
var chk = VisualTreeHelpers.FindAncestor<CheckBox>((DependencyObject)e.OriginalSource, "TestCheckBox");
if (chk == null)
e.Handled = true;
}
I using my custom VisualTreeHelpers again to navigate the visual tree and find out if the CheckBox was clicked on, and cancelling the event if the user clicked on anywhere other than the CheckBox.
As for your 2nd request of adding a CheckBox to SelectAll or UnselectAll items, this would once again be dependent on if your selection is part of the UI or the data.
If it's part of the UI, simply add a CheckBox to the DataGridTemplateColumn.HeaderTemplate, and when it's clicked, loop through the DataGrid.Rows, find the CheckBox in the first column, and check or uncheck it.
If it's part of the data you could still do the same thing (only set the bound value in the DataGrid.Items instead of the CheckBox.IsChecked from the DataGrid.Rows), or you could do as Adolfo Perez suggested, and bind it to a property on the ViewModel.
For an MVVM Solution you could try this:
<StackPanel>
<DataGrid ItemsSource="{Binding Path=TestItems}" AutoGenerateColumns="False" Name="MyDataGrid"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}" Width="50" >
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate x:Name="dtAllChkBx">
<CheckBox Name="cbxAll" Content="All" IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
<DataGridTemplateColumn Header="Name" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
In your ViewModel:
private void PopulateTestItems()
{
TestItems = new ObservableCollection<TestItem>();
for (int i = 0; i < 5; i++)
{
TestItem ti = new TestItem();
ti.Name = "TestItem" + i;
ti.IsSelected = true;
TestItems.Add(ti);
}
}
private bool _AllSelected;
public bool AllSelected
{
get { return _AllSelected; }
set
{
_AllSelected = value;
TestItems.ToList().ForEach(x => x.IsSelected = value);
NotifyPropertyChanged(m => m.AllSelected);
}
}
private ObservableCollection<TestItem> _TestItems;
public ObservableCollection<TestItem> TestItems
{
get { return _TestItems; }
set
{
_TestItems = value;
NotifyPropertyChanged(m => m.TestItems);
}
}
And finally the sample Model class:
public class TestItem : ModelBase<TestItem>
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
NotifyPropertyChanged(m => m.Name);
}
}
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
NotifyPropertyChanged(m => m.IsSelected);
}
}
}
Most of the code above should be self-explanatory but if you have any question let me know
Your view can be something like
<DataGrid Name="SomeDataGrid" Grid.Row="0" ItemsSource="{Binding Path=SomeCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.AllItemsAreChecked}" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:SomeType}">
<CheckBox Focusable="False" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="RandomNumber" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:SomeType}">
<TextBlock Text="{Binding Path=RandomNumber}" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Date" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:SomeType}">
<TextBlock Text="{Binding Path=Date}" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Time" Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:SomeType}">
<TextBlock Text="{Binding Time}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And in viewmodel
SomeCollection binding property is an observablecollection
sometype contains properties like IsSelected , RandomNumber ,Date , Time
for eg:
class ViewModel
{
public ObservableCollection<SomeType> SomeCollection{get;set;}
}
class SomeType
{
public string Date {get;set;}
public string Time {get;set;}
public string RandomNumber {get;set;}
public bool IsSelected {get;set;}
}

how to access the label inside datatemplate

hello everybody i have a listbox within which is a datatemplate.Inside it is checkbox,textbox,label...Wat i want is to get the value of the label wen the checkbox is unchecked? or any alternative as to how to access the label value but only wen the checkbox is unselected............PLease help me out.
the code is as
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding RelativeSource{RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked">
<TextBlock FontSize="11" Text="{Binding subject_name}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding subject_id}" Visibility="Visible">
</Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Since you're using binding on label, I'd go for accessing subject_id from the object the datatemplate is describing. Like this:
var subjectId = dataBoundItem.subject_id;
That's the correct way to go with MVVM and bindings.
UPDATE:
Here's the basic MVVM approach to solving this problem. First of all, I've cleaned up a bit your listbox declaration and added a trigger that sets IsSelected binding:
<ListBox ItemsSource="{Binding}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked_1">
<TextBlock FontSize="11" Text="{Binding SubjectName}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding SubjectId}" Visibility="Visible"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here, whenever value IsSelected on individual ListBoxItem changes, the "IsSelected" binding of the viewModel is changed. Here's the model:
public class SelectableItem : INotifyPropertyChanged
{
private string _subjectId;
private bool _isSelected;
private string _subjectName;
public string SubjectId
{
get { return _subjectId; }
set { _subjectId = value; OnPropertyChanged("SubjectId"); }
}
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; OnPropertyChanged("IsSelected"); }
}
public string SubjectName
{
get { return _subjectName; }
set { _subjectName = value; OnPropertyChanged("SubjectName"); }
}
// .. INotifyPropertyChangedImplementation
Your IsSelected will be set to true whenever relevant item is selected and to false whenever it is unselected. You may put your code in to the "set" item of the "IsSelected" property and check (value == false) and execute necessary piece of code as you see fit. This would be MVVM approach to the matter.
Using the event, you can do as follows:
private void chkSubject_Unchecked_1(object sender, RoutedEventArgs e)
{
FrameworkElement control = sender as FrameworkElement;
if (control == null)
return;
SelectableItem item = control.DataContext as SelectableItem;
if (item == null)
return;
string yourValue = item.SubjectId;
}
I strongly recommend you read about MVVM and bindings.
What about using the Checked and UnChecked events of your CheckBox, so that you can retrieve the value of subject_id which is binded to your Label.

ListBox with hyperlink -> selection changed

I want to do with xaml bindings such feature:
Listbox contains hyperlinks.
When hyperlink clicked - we go to another frame
But also SelectedItem must changed, and on another frame we show info about selected item.
I want it without subscribing click/selected events. Only declarative
Example of my listbox
<ListBox Grid.Row="1"
x:Name="OrderTypesListBox"
ItemsSource="{Binding OrderTypes, Mode=OneWay}"
SelectedItem="{Binding SelectedCall.OrderType, Mode=TwoWay}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<HyperlinkButton Style="{StaticResource LinkStyle}" NavigateUri="/WindowPage" TargetName="ContentFrame" Content="WindowPage"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now solve like that
<ListBox Grid.Row="1"
x:Name="OrderTypesListBox"
ItemsSource="{Binding OrderTypes, Mode=OneWay}"
SelectedItem="{Binding SelectedCall.OrderType, Mode=TwoWay}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<HyperlinkButton
TargetName="ContentFrame"
NavigateUri="{Binding OrderTypeNextPage}"
Content="{Binding Name}"
Click="HyperlinkButton_Click"
Tag="{Binding}"
/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
OrderTypesListBox.SelectedItem = (sender as HyperlinkButton).Tag;
}
Don't use a HyperlinkButton. Perform the needed actions when the SelectedItem changes in your ViewModel.
Edit: If you need to respond to all click events even if the item is already selected then you can use a Behavior to do this. Just create a behavior for the TextBlock that navigates on the TextBlock click event.
Edit2: Behaviors are pretty simple to code up and easy to use (and don't break the MVVM paradigm).
public class NavigatingTextBlockBehavior : Behavior<TextBlock>
{
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseDown);
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
NavigationService.Navigate(new Uri("/WindowPage"));
}
}

Wrong selection in ListBox with VirtualizationMode="Recycling" and SeclectionMode="Extended"?

I have a really strage behaviour. I have a ListBox in the View with a DataTemplate for its items including ViewModels. I bind the IsSelected to my ViewModel and use SelectionMode="Extended". Everything works fine.
BUT if I add VirtualiuationMode="Recycling" the I get the wrong items.
To reproduce: select items with Ctrl, then scroll down and select just one item. The normal behaviour deselects all items and just select the last one without holded Ctrl.
But if I check my ViewModel all the old items are selected!?!
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding People}" MaxHeight="100"
SelectionMode="Extended"
VirtualizationMode="Recycling">
<!--VirtualizingStackPanel.IsVirtualizing="True">-->
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<views:PeopleView />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Click="Button_Click">
OK
</Button>
</StackPanel>
</Grid>
The item template
<UserControl x:Class="WpfApplication1.View.PeopleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="B"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Path=Name}"
Name="tbx_Name"
Grid.Column="0"/>
<CheckBox IsChecked="{Binding Path=IstAktiv}"
Name="cbx_IstAktiv"
Grid.Column="1"/>
</Grid>
Any idea?
I got a workaround but why do I have to change it "manually" in the change event and not by databinding?
private void Lbx_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox lbx = (ListBox)sender;
foreach (PersonViewModel item in lbx.Items)
{
item.IsSelected = lbx.SelectedItems.Contains(item);
}
}
Another option related to KCT's earlier answer is to use the AddedItems and RemovedItems from the SelectionChangedEventArgs and target the changes, such as:
private void Lbx_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems)
{
((PersonViewModel)item).IsSelected = true;
}
foreach (var item in e.RemovedItems)
{
((PersonViewModel)item).IsSelected = false;
}
}
This may give better performance with larger collections (I've got about 15,000 entries in a Virtualizing Tile Panel in a ListBox).

Resources