Selected item in a listview doesn't unselect - wpf

I can select multiple items in a listview. But if i click on one, it turns blue. That's normal, so that shows it is selected. But if i click again on the same item, it doesnt uncheck. So i can't change my selection. Somebody who knows how to fix this stupid little problem?
Edit: This is my listview:
<ListView Height="155" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectedItem="{Binding Path=SelectedQuestionDropList, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True" SelectionMode="Multiple" Margin="0,0,542,436" Background="#CDC5CBC5"
dd:DragDrop.DropHandler="{Binding}" Name="DropListView" ItemsSource="{Binding Path=SelectedExaminationQuestions,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" SelectionChanged="ListView_SelectionChanged_1" VerticalAlignment="Bottom">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Verkorte naam" Width="Auto" DisplayMemberBinding="{Binding Path=ShortName}" />
<GridViewColumn Header="Omschrijving" Width="Auto" DisplayMemberBinding="{Binding Path=Description}" />
<GridViewColumn Header="Type" Width="Auto" DisplayMemberBinding="{Binding Path=Type}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>

I was facing a similar problem and found that, while left-click always select the item pointed to, you can use Ctrl + left-click to toggle selection in a list view. This is the default behavior.

You can write a wpf behaviour. Something like:
public class ListViewBehaviour
{
/// <summary>
/// Enfoca automaticament el item sel·leccionat
/// </summary>
public static readonly DependencyProperty AutoUnselectItemProperty =
DependencyProperty.RegisterAttached(
"AutoUnselect",
typeof(bool),
typeof(ListViewBehaviour),
new UIPropertyMetadata(false, OnAutoUnselectItemChanged));
public static bool GetAutoUnselectItem(ListView listBox)
{
return (bool)listBox.GetValue(AutoUnselectItemProperty);
}
public static void SetAutoUnselectItem(ListView listBox, bool value)
{
listBox.SetValue(AutoUnselectItemProperty, value);
}
private static void OnAutoUnselectItemChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var listView = source as ListView;
if (listView == null)
return;
if (e.NewValue is bool == false)
listView.SelectionChanged -= OnSelectionChanged;
else
listView.SelectionChanged += OnSelectionChanged;
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
// TODO write custom selection behaviour
}
}
And to apply it to a listview:
<ListView bb:ListViewBehaviour.AutoUnselect="True">
...
</ListView>

Related

WPF Bind UserControl Property inside of the DataTemplate of a GridViewColumn.CellTemplate

I'm trying to figure out how to bind a property of a custom user control that is placed inside of the cell template of a list view control but it's not working. All of the DisplayMemberBinding fields are working as expected, and I'm getting the correct values, but inside of that custom control, nothing is updating.
WPF LIstView Control
<ListView Margin="10" x:Name="lvHistory">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Database" Width="150" DisplayMemberBinding="{Binding ActiveBackup.Database.Name, Mode=TwoWay}" />
<GridViewColumn Header="Start Time" Width="130" DisplayMemberBinding="{Binding ActiveBackup.StartTime, Mode=TwoWay}" />
<GridViewColumn Header="Time Elapsed" Width="100" DisplayMemberBinding="{Binding ActiveBackup.TimeElapsed, Mode=TwoWay}" />
<GridViewColumn Header="P2" Width="100" DisplayMemberBinding="{Binding Progress, Mode=TwoWay}" />
<GridViewColumn x:Name="progressColumn" Header="Progress" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<local:cProgressBarSmall x:Name="pr1" Value="{Binding Progress, Mode=TwoWay}" Visibility="Visible" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code-Behind in the cProgressBarSmall control.
public partial class cProgressBarSmall : UserControl
{
public ActiveBackup ActiveBackup { get; set; }
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(decimal), typeof(cProgressBarSmall));
private decimal _value;
public decimal Value
{
get
{
return (decimal) GetValue(ValueProperty);
}
set
{
_value = value;
SetValue(ValueProperty, value);
p1.Value = value.ToDoubleNotNull();
pLabel.Text = value.ToPercent(0);
if (value == 0)
{
p1.Visibility = Visibility.Hidden;
pLabel.Visibility = Visibility.Hidden;
}
else if (value.ToDoubleNotNull() >= p1.Maximum)
{
pLabel.Text = "Finished!";
pLabel.Foreground = new SolidColorBrush(Colors.Black);
}
}
}
}
}
I can't find a way to access the "pr1" because it's in a DataTemplate and therefore not directly accessible from the code-behind. Does binding not work through? The column before it (the "P2" column) is just at test column I put in just to make sure that the value is in fact updating and it is and that displays correctly, however the "progressColumn" always just shows the default value.
Is there anything special to data binding inside of a ListView.View > GridView > GridViewColumn > GridViewColumn.CellTemplate > DataTemplate hierarchy?
First, if you put a breakpoint in your setter you'll find that it's not hit by the binding. That's because the Binding is setting the dependency property, not the C# property. They're different. The C# property with get/set is an optional wrapper around the dependency property.
The correct way to do this is to have little or no code behind (code behind's not evil; you just don't need any for this one), but use a binding in the usercontrol xaml to update the UI. You can hide and show controls, and update label text, with style triggers in the usercontrol XAML. You don't need any code behind for this.
But here's the simplest way to adapt your existing code to something that works.
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(decimal), typeof(cProgressBarSmall),
new PropertyMetadata(0m, Value_ChangedCallback));
// Has to be static
private static void Value_ChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((cProgressBarSmall)d).OnValueChanged();
}
private void OnValueChanged()
{
p1.Value = Value.ToDoubleNotNull();
pLabel.Text = Value.ToPercent(0);
if (Value == 0)
{
p1.Visibility = Visibility.Hidden;
pLabel.Visibility = Visibility.Hidden;
}
else if (Value.ToDoubleNotNull() >= p1.Maximum)
{
pLabel.Text = "Finished!";
pLabel.Foreground = new SolidColorBrush(Colors.Black);
}
}

focus on last line of a listview binded to a collection - wpf mvvm

In a wpf project i've a listview "binded" to a collection
Every time i add an item to the collection i'd like the focus on the listview goes to the last line (to the addeed one)
how to do that?
//XAML
<ListView
x:Name="logListActions"
Height="200"
MinHeight="150"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="WhiteSmoke"
BorderThickness="1"
ItemsSource="{Binding LogMessages}">
<ListView.View>
<GridView AllowsColumnReorder="True">
<GridViewColumn
Width="110"
DisplayMemberBinding="{Binding When}"
Header="Data" />
<GridViewColumn
Width="Auto"
DisplayMemberBinding="{Binding Message}"
Header="Messaggio" />
</GridView>
</ListView.View>
</ListView>
//ViewModel
public ObservableCollection<LogMessage> LogMessages
{
get { return _logMessageList; }
set
{
_logMessageList = value;
OnPropertyChanged("LogMessages");
}
}
After adding new item into Listbox, call below code from code behind:
logListActions.ScrollIntoView(item);
logListActions.SelectedItem = item;
Bind the SelectedItem property of the ListView to a LogMessage property in your view model and set the latter to the last added LogMessage object:
<ListView ... ItemsSource="{Binding LogMessages}" SelectedItem="{Binding SelectedLogMessage}">
private LogMessage _selected;
public LogMessage SelectedLogMessage
{
get { return _selected; }
set { _selected = value; OnPropertyChanged("SelectedLogMessage"); }
}
This will select the last row. You could then handle the SelectionChanged event in the view to focus and highlight it:
private void logListActions_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems != null && e.AddedItems.Count > 0)
{
Dispatcher.BeginInvoke(new Action(() =>
{
ListViewItem lvi = logListActions.ItemContainerGenerator.ContainerFromItem(e.AddedItems[0]) as ListViewItem;
if (lvi != null)
lvi.Focus();
}), System.Windows.Threading.DispatcherPriority.Background);
}
}

Wpf listview item object doubleclick

I have a list of objects that get dynamically created when the window opens. For instance:
//Set content for listview sentitems
inbox.ItemsSource = from email in _dataDC.emails
where email.from == _username
orderby email.time descending
select email;
My xaml:
<TabItem Header="Inbox" Height="30">
<TabItem.Content>
<ListView Name="inbox" BorderThickness="2" Margin="5,0,-5,0">
<ListView.View>
<GridView>
<GridViewColumn Header="Van" Width="70" DisplayMemberBinding="{Binding from}" />
<GridViewColumn Header="Onderwerp" Width="120" DisplayMemberBinding="{Binding subject}" />
<GridViewColumn Header="Op" Width="130" DisplayMemberBinding="{Binding time}" />
</GridView>
</ListView.View>
</ListView>
</TabItem.Content>
</TabItem>
When an item in the list is doubleclicked, I simply want to open a new window. Object gets passed to the new window, where I do something with it. Any simple solution?
Try this out...
XAML
<ListView Name="inbox" BorderThickness="2" Margin="5,0,-5,0" MouseDoubleClick="inbox_OnMouseDoubleClick">
C#
private void inbox_OnMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// Assumes your NewWindow class has a constuctor that takes the Email type.
NewWindow window = new NewWindow((Email)inbox.SelectedItem);
window.Show();
}
Use ListView's MouseDoubleClick.
XAML:
<ListView Name="inbox" BorderThickness="2" Margin="5,0,-5,0" MouseDoubleClick="ListView_MouseDoubleClick">
Code Behind:
private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var item = (sender as ListView).SelectedItem;
if (item != null)
{
//use the item here and pass to the new window
NewModal s = new NewModal(Email)item);
}
}

Is there a simple way to have a ListView automatically scroll to the most recently added item without having to write any code in the code behind?

I have a ListView which is bound to an observable collection. As items are added to the observable collection, the listview does not automatically scroll to show the most recently added item. I am trying to adhere to good WPF practices and would like to avoid writing any code in the view's code-behind. Is there a simple way to accomplish this through the XAML or the corresponding Model's code?
<ListView HorizontalAlignment="Center" ItemsSource="{Binding ScenarioSnippets}" Background="{x:Null}"
BorderBrush="{x:Null}" BorderThickness="0" SelectionMode="Single" VerticalAlignment="Top"
HorizontalContentAlignment="Center" IsSynchronizedWithCurrentItem="True">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Focusable" Value="False" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView Selector.IsSelected="True" AllowsColumnReorder="False">
<GridView.Columns>
<GridViewColumn CellTemplate="{StaticResource ScenarioSnippetItemCellTemplate}"
HeaderContainerStyle="{StaticResource GridViewHeaderStyle}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
You could use a Blend behavior:
public class AutoScrollToLastItemBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
var collection = AssociatedObject.Items.SourceCollection as INotifyCollectionChanged;
if (collection != null)
collection.CollectionChanged += collection_CollectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
var collection = AssociatedObject.Items.SourceCollection as INotifyCollectionChanged;
if (collection != null)
collection.CollectionChanged -= collection_CollectionChanged;
}
private void collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
ScrollToLastItem();
}
}
private void ScrollToLastItem()
{
int count = AssociatedObject.Items.Count;
if (count > 0)
{
var last = AssociatedObject.Items[count - 1];
AssociatedObject.ScrollIntoView(last);
}
}
}
XAML:
<ListView ItemsSource="...">
<i:Interaction.Behaviors>
<local:AutoScrollToLastItemBehavior />
</i:Interaction.Behaviors>
</ListView>
(the Behavior and Interaction classes can be found in System.Windows.Interactivity.dll in the Blend SDK)
You need to create an Attached Behavior which will allow your ListView to honor the MVVM paradigm which is what you are ultimately after.
A solution/example with ListBox (easily modified for a ListView) can be found here.

GridView DoubleClick

I have a GridView where I want to detect a doubleclick event on the items in the list, i do it as follows:
<ListView>
<ListView.View >
<GridView >
<GridViewColumn Header="FileName">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding FileName}" MouseDoubleClick="Configuration_MouseDoubleClick"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding CreationDate}" Header="Date"/>
</GridView>
</ListView.View>
</ListView>
The problem is that I can only detect doubleclicks by attaching it to the control in the template.
How can I attach the MouseDoubleClick event to the whole ListViewItem? Is there any solution for that with PRISM?
You can add the MouseDoubleClick event to ListViewItem in the ItemContainerStyle like this
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Code behind..
void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
//...
}
If you're doing MVVM, you can bridge the gap between codebehind and your viewmodel the usual way--by using an attached behavior:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
public sealed class HandleDoubleClickBehavior
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
"Command", typeof (ICommand), typeof (HandleDoubleClickBehavior), new PropertyMetadata(default(ICommand), OnComandChanged));
public static void SetCommand(DependencyObject element, ICommand value)
{
element.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject element)
{
return (ICommand) element.GetValue(CommandProperty);
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
"CommandParameter", typeof (object), typeof (HandleDoubleClickBehavior), new PropertyMetadata(default(object)));
public static void SetCommandParameter(DependencyObject element, object value)
{
element.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject element)
{
return (object) element.GetValue(CommandParameterProperty);
}
private static void OnComandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var c = d as Control;
if (c == null)
throw new InvalidOperationException($"can only be attached to {nameof(Control)}");
c.MouseDoubleClick -= OnDoubleClick;
if (GetCommand(c) != null)
c.MouseDoubleClick += OnDoubleClick;
}
private static void OnDoubleClick(object sender, MouseButtonEventArgs e)
{
var d = sender as DependencyObject;
if (d == null)
return;
var command = GetCommand(d);
if (command == null)
return;
var parameter = GetCommandParameter(d);
if (!command.CanExecute(parameter))
return;
command.Execute(parameter);
}
}
With that in your toolbox, you can write XAML like this (assuming PersonViewModel contains the string properties Name and Title, and an ICommand property named SayHiCommand that expects a string parameter):
<ListView ItemsSource="{Binding Persons}" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="local:HandleDoubleClickBehavior.Command" Value="{Binding SayHiCommand}" />
<Setter Property="local:HandleDoubleClickBehavior.CommandParameter" Value="{Binding Name}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}" />
</GridView>
</ListView.View>
</ListView>
For those here that are using a framework like Prism where the main usercontrol containing the Listview is a View (not a window) that is bound to a viewmodel.
The answer of dlf is the best with these small tweaks:
{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.SayHiCommand}
And in the special behavior attached property you change the ICommand cast to a DelegateCommand cast.
Works like a charm,
Thank you very much.

Resources