WPF: Prevent ListViewItem from being selected on Shift + Arrow - wpf

How do I prevent a ListViewItem from being selected when I click on an item and press (Down/Up) Arrow? I don't want to disable it, I just want it to not be selectable on Shift+Arrow. Some of my listviewitems need to be selectable and some others not.
Sample:
[ListViewItem 1]
[ListViewItem 2]
[ListViewItem 3]
User clicks on ListviewItem1, it gets selected.
User then presses SHIFT+DOWN ARROW,=> ListViewItem 2 is not selected.
At this point either ListViewItem3 is selected or it might take another SHIFT+DOWN ARROW to select ListViewItem3 (it doesn't matter as long as it gets selected sometime).
(In my project the actual ListViewItem 2 is actually NOT a regular item on 1 row, it is a vertical item that spans several rows in just one column. My ListView source has a compositecollection of very different items; I am trying to select what appears to be 'regular row items' only)

You can accomplish this by using a Property of your class such as IsSelected. Here is some sample code for what that Property would look like.
public bool IsSelected
{
get
{
return isSelected; //return the value of isSelected
}
set
{
if (isSelectable) //if the item is allowed to be selected
{ //then update the value of isSelected
isSelected = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
In this example it is assumed that "isSelectable" is a value that is set in the constructor when your class is being initialized. If you don't already have INotifyPropertyChanged implemented, it needs to be... and the PropertyChanged event needs to be declared.
In addition, you will also want a Style that binds the selection of ListView items to this property. Here is an example of a Style that could accomplish this.
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.Resources>
You can take this one step further, and also bind to the Focusable Property. This will make it so that when you press SHIFT+DOWN it will skip any items that you cannot select and select the next item that is selectable. I would accomplish this in a manner similar to the following.
public bool Focusable
{
get
{
return isSelectable; //if the item is selectable, then it is focusable
}
}
You would also need to create a binding for this in your Style by creating an additional Setter. This could be done the following way, inside the same Style as before.
<Setter Property="Focusable" Value="{Binding Focusable}"/>
Try it both with and without that last Setter and see which implementation suits your needs best.
--------- UPDATE ---------
If you would like to only select items on mouse click, you could do so by subscribing to the ListView PreviewMouseLeftButtonDown event. Inside of the event handler, you would need to first get the clicked item, and call a function on your class which will override the selection. Here is an example of how that is done.
This function would exist in your UI code-behind and must be subscribed to by your ListView:
private void myListView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListViewItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
object obj = myListView.ItemContainerGenerator.ItemFromContainer(dep);
if (obj is MyClass)
{
foreach (MyClass i in myListView.Items)
i.OverrideSelect(false); //unselect all items
MyClass item = (MyClass)obj;
item.OverrideSelect(true); //select the item clicked
}
}
(Note: replace "MyClass" with your class type).
This function would exist in your class file:
public void OverrideSelect(bool selected)
{
isSelected = selected;
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}

Related

Bubbling up WPF Datagrid Item events

I've a datagrid with an ItemsSource of ObservableCollection (OC) of objects. When an item's property changes, I want to work on the OC itself .
E.g. I've an item which is approved for uploading to our database. However, I need to loop through the OC to check if other items exist in the collection which already fit the set criteria, so that I may actually not have to upload the selected item.
On the datagrid, when I tick the checkbox of an item, it will change the boolean value (e.g. "IsToUpload") of the item, and an event should trigger on the property change.
I'm assuming I will then need to 'bubble up' my event notifications to the datagrid/mainwindow class, where I can then work on the OC. How may I do this, and if this is not the correct way, what should I be doing?
I've followed Aran Mulholland's class structure to colour my rows dynamically: Coloring WPF DataGridRows one by one
So my class structure is roughly as follows:
MainWindow -> DataGrid
-> ObservableCollection<ItemObjectViewModel:NotificationObject>
ItemObject : INotifyPropertyChanged //this class is where I
//store my item variables. It is referenced through properties
//in the ItemObjectViewModel.
Event bubling \ routing etc works for dependency objects in a visual \ logical tree. Your NotificationObject is not a dependency object and neither is it hosted in the visual tree.... What we have in visual tree are the checkboxes (that are bound to your NotificationObject).
Non MVVM
In you DataGrid you would have to Tag your Checkboxes with some identification and then use ButtonBase.Click="" event at datagrid level which will be handled for any click event bubbled for any button based eleemnt (such as buttons, menuitems, togglebuttons, checkboxes, radioboxes, comboboxes) that gets clicked in the entire visual tree of the datagrid.
In the handler verify if the e.OriginalSource is a checkbox and that its Tag is same as the identification value we have set in the XAML of the datagrid. That way we know that the CheckBox is clicked.
E.g.
<DataGrid AutogenerateColumns="False"
ItemsSource="{Binding NotificationObjectCollection}"
ButtonBase.Clicked="OnNotificationCheckBoxClicked">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsClicked}"
Header="Click?">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Tag" Value="IsClickCheckBox" />
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
private void OnNotificationCheckBoxClicked
(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is CheckBox)
{
if (((CheckBox)e.OriginalSource).Tag == "IsClickCheckBox")
{
var notificationObject
= ((CheckBox)e.OriginalSource).DataContext
as NotificationObject;
if (notificationObject.IsClicked) { }
else { }
}
}
}
MVVM
The only way MVVM can notify the ancestor object in the visual is by using Command execution as the underlying NotificationObject gets checked (setter is called) we execute the command supplied to the NotificationObject.
Use the weak reference based RelayCommand or DelegateCommand (as available on the internet) for this purpose.
Add a new NotificationObject constructor
private ICommand _isClickedCommand;
public NotificationObject(ICommand isClickedCommand)
{
_isClickedCommand = isClickedCommand;
}
private bool _isClicked;
public bool IsClicked
{
get
{
return _isClicked;
}
set
{
if (_isClicked != value)
{
_isClicked = value;
OnPropertyChanged("IsClicked");
isClickedCommand.Execute(this);
}
}
}
Using the notification object
public class ItemObjectViewModel
{
private DelegateCommand<NotificationObject>
_notificationObjectClickedCommand
= new DelegateCommand<NotificationObject>(
OnNotificationObjectCommandExecute);
....
private void PopulateCollection()
{
NotificationObjectCollection
= new ObservableCollection<NotificationObject>();
NotificationObjectCollection.Add(
new NotificationObject(_notificationObjectClickedCommand));
}
private void OnNotificationObjectCommandExecute(
NotificationObject notificationObject)
{
if (notificationObject.IsClicked) { }
else { }
}
}
You can also achieve the ICommand based behavior in non MVVM scenario by using 'RoutedCommand'
Let me know if this helps...

WPF ItemsControl get container from data object (TreeView, Multiselect)

How can I get the Container for an object in WPF ItemsControl.
I am writing a multiselect treeview with bindable SelectedItem und SelectedItems Dependency Properties. So long everything works just fine. The only thing is, when I click on an item in the tree with pressed ctrl a second time this item should not be selected but the last previous selected item. The TreeView contains a private Method called ChangeSelection. As far as i understand the first parameter is the Container, the second is the TreeViewItem and the last wherether the item shall be selected or not.
I implement the multiselection with catching the SelectedItemChanged event.
This code works for the new selected item
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var view = ItemContainerGenerator.ContainerFromItem(e.NewValue) as TreeViewItem;
// ...
}
BUT if i want to get the TreeViewItem from an item saved in an ObservableCollection... it will not work.
EDIT: Ok, as i found out. The code above works only for the first level of items...
EDIT: The solution for this problem isn't trivial. It is possible to find the selected treeview item by using a viewmodel (f.e. an interface which provides basics like: IsSelected, IsExpanded, IsEnabled and Parent). You can search the TreeViewItem like this:
if (treeViewItem.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
EventHandler eventHandler = null;
eventHandler = delegate
{
treeViewItem.ItemContainerGenerator.StatusChanged -= eventHandler;
// Call the search function recursive XYZ(tree, treeViewItem.ItemContainerGenerator.ContainerFromItem(nextLevelItem) as TreeViewItem);
};
// wait for the containers to be generated
treeViewItem.ItemContainerGenerator.StatusChanged += eventHandler;
}
else
{
// Call the search function recursive XYZ(tree, treeViewItem.ItemContainerGenerator.ContainerFromItem(nextLevelItem) as TreeViewItem);
}

How to delete a ListViewItem when bound to a DataView

How do I delete a selected ListViewItem from a WPF ListView when the ItemsSource is set to a DataView? I can get the ListViewItem that was selected and then how do remove the actual row in the DataView?
DataView dv = (DataView)myListView.ItemsSource;
ListViewItem lvi = (ListViewItem)myListView.ItemContainerGenerator.ContainerFromItem(myListView.SelectedItem);
<Delete ListViewItem here>
When you bind your collection to the listview, use ListCollectionView instead of DataView. Can be easily done like this (where dataView is of type DataView):
ListCollectionView lcv = new ListCollectionView(dataView);
myListView.ItemsSource = lcv;
Now when you need to delete any object, just do this:
ListCollectionView lcv = (ListCollectionView) myListView.ItemsSource;
lcv.Remove(myListView.SelectedItem);
And after deleting, just refresh the view:
lcv.Refresh();
or
((ListCollectionView)myListView.ItemsSource).Refresh();
Consider using the M-V-VM pattern to separate the notion of removing an item from your list of data objects and DIRECTLY removing them from your current UI implementation. The two do not need to know about each other, aside from Bindings.
When you use the MVVM pattern, expose a boolean "IsSelected" property in your ViewModel.
public class SimpleViewModel : BaseViewModel //For INotifyPropertyChanged, etc
{
public IList<SimpleBusinessObject> ViewModelItems;
public SimpleViewModel()
{
ViewModelItems = new ObservableList<SimpleBusinessObjectViewModel>();
}
}
public class SimpleBusinessObjectViewModel
{
public bool ViewModelIsSelected { get; set; }
public SimpleBusinessObjectViewModel()
{
ViewModelIsSelected = false;
}
}
Next, in your View try something like this:
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Setter Property="IsSelected" Value="{Binding ViewModelIsSelected}"
</Style.Triggers>
</Style>
<ListView ItemsSource={Binding ViewModelItems}>
//here you can insert how you want to display a ListViewItem
</ListView>
This will let you add, edit, and remove items in your ViewModel's List -- just like if it were the actual ListView. From here, you can also check each item's IsSelected (that responds to mouse interactions with the ListView) without actually checking the ListViewItem. This will be a much cleaner, maintainable solution.

ListView Binding refresh suggestion in WPF

I have an ObservableCollection bound to a ListBox and have a highlight mechanism set up with DataTriggers, when I had a simple set of highlighters (debug, warning, etc) I could simply enumerate the style with several data-triggers bound to the view model that exposes those options.
I have now upgraded the system to support multiple userdefined highlighters which expose themselves with IsHighlighted(xxx) methods (not properties).
How can I make the the ListView aware that the visual state (style's datatrigger) has changed? Is there a "refreshed" event I can fire and catch in a DataTrigger?
Update:
I have a DataTrigger mapped to an exposed property Active which simply returns a value of true, but despite that there is no update:
<DataTrigger Binding="{Binding Highlight.Active}"
Value="true">
<Setter Property="Background"
Value="{Binding Type, Converter={StaticResource typeToBackgroundConverter}}" />
<Setter Property="Foreground"
Value="{Binding Type, Converter={StaticResource typeToForegroundConverter}}" />
</DataTrigger>
When the condition of a DataTrigger changes, this should automatically cause the parent UI element to refresh.
A couple of things to check:
1. The input data of the trigger is actually changing as you expect it to.
2. The input data of the trigger binds to a dependency property. Otherwise, you will never know when the value updates.
If you showed us the appropiate parts of your XAML, that would help a great deal.
If you just want to set the colour of the item somehow, you could write a converter that does what you want:
<Thing Background="{Binding Converter={StaticResource MyItemColorConverter}}" />
In this case, the converter could call your IsHighlighted(xxx) method and return the appropriate colour for the Thing.
If you want to set more than one property, you could use multiple converters, but the idea starts to fall apart at some point.
Alternatively, you could use a converter on your DataBinding to determine whether the item in question falls into a certain category and then apply setters. It depends upon what you need!
EDIT
I have just re-read your question and realised I'm off the mark. Whoops.
I believe you can just raise INotifyPropertyChanged.PropertyChanged with a PropertyChangedEventArgs that uses string.Empty, and that forces the WPF binding infrastructure to refresh all bindings. Have you tried that?
I'm going to answer my own question with an explanation of what I needed to do.
It's a long answer as it seems I kept hitting against areas where WPF thought it knew better and would cache. If DataTrigger had a unconditional change, I wouldn't need any of this!
Firstly, let me recap some of the problem again. I have a ListView that can highlight different rows with different styles. Initially, these styles were built-in types, such as Debug and Error. In these cases I could easily latch onto the ViewModel changes of them as DataTriggers in the row-style and make each update immediately.
Once I upgraded to allow user-defined highlighters, I no longer had a property to latch onto (even if I dynamically created them, the style wouldn't know about them).
To get around this, I have implemented a HighlightingService (this can be discovered at any point by using my ServiceLocator and asking for a IHightlightingServce supporting instance). This service implements a number of important properties and methods:
public ObservableCollection<IHighlighter> Highlighters { get; private set; }
public IHighlighterStyle IsHighlighted(ILogEntry logEntry)
{
foreach (IHighlighter highlighter in Highlighters)
{
if ( highlighter.IsMatch(logEntry) )
{
return highlighter.Style;
}
}
return null;
}
Because the Highlighters collection is publicly accessible, I decided to permit that users of that collection could add/remove entries, negating my need to implement Add/Remove methods. However, because I need to know if the internal IHighlighter records have changed, in the constructor of the service, I register an observer to its CollectionChanged property and react to the add/remove items by registering another callback, this allows me to fire a service specific INotifyCollectionChanged event.
[...]
// Register self as an observer of the collection.
Highlighters.CollectionChanged += HighlightersCollectionChanged;
}
private void HighlightersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var newItem in e.NewItems)
{
System.Diagnostics.Debug.Assert(newItem != null);
System.Diagnostics.Debug.Assert(newItem is IHighlighter);
if (e.NewItems != null
&& newItem is IHighlighter
&& newItem is INotifyPropertyChanged)
{
// Register on OnPropertyChanged.
IHighlighter highlighter = newItem as IHighlighter;
Trace.WriteLine(string.Format(
"FilterService detected {0} added to collection and binding to its PropertyChanged event",
highlighter.Name));
(newItem as INotifyPropertyChanged).PropertyChanged += CustomHighlighterPropertyChanged;
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var oldItem in e.OldItems)
{
System.Diagnostics.Debug.Assert(oldItem != null);
System.Diagnostics.Debug.Assert(oldItem is IHighlighter);
if (e.NewItems != null
&& oldItem is IHighlighter
&& oldItem is INotifyPropertyChanged)
{
// Unregister on OnPropertyChanged.
IHighlighter highlighter = oldItem as IHighlighter;
Trace.WriteLine(string.Format(
"HighlightingService detected {0} removed from collection and unbinding from its PropertyChanged event",
highlighter.Name));
(oldItem as INotifyPropertyChanged).PropertyChanged -= CustomHighlighterPropertyChanged;
}
}
}
}
private void CustomHighlighterPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if ( sender is IHighlighter )
{
IHighlighter filter = (sender as IHighlighter);
Trace.WriteLine(string.Format("FilterServer saw some activity on {0} (IsEnabled = {1})",
filter.Name, filter.Enabled));
}
OnPropertyChanged(string.Empty);
}
With all of that, I now know whenever a user has changed a registered highlighter, but it has not fixed the fact that I can't associate a trigger to anything, so I can reflect the changes in the displayed style.
I couldn't find a Xaml only way of sorting this, so I made a custom-control containing my ListView:
public partial class LogMessagesControl : UserControl
{
private IHighlightingService highlight { get; set; }
public LogMessagesControl()
{
InitializeComponent();
highlight = ServiceLocator.Instance.Get<IHighlightingService>();
if (highlight != null && highlight is INotifyPropertyChanged)
{
(highlight as INotifyPropertyChanged).PropertyChanged += (s, e) => UpdateStyles();
}
messages.ItemContainerStyleSelector = new HighlightingSelector();
}
private void UpdateStyles()
{
messages.ItemContainerStyleSelector = null;
messages.ItemContainerStyleSelector = new HighlightingSelector();
}
}
This does a couple of things:
It assigns a new HighlightingSelector to the ItemContainerStyleSelector (the ListView is called messages).
It also registers itself to the PropertyChanged event of the HighlighterService which is a ViewModel.
Upon detecting a change, it replaces the current instance of HighlightingSelector on the ItemContainerStyleSelector (note, it swaps to null first as there is a comment on the web attributed to Bea Costa that this is necessary).
So, now all I need is a HighlightingSelector which takes into account the current highlighting selections (I know that should they change, it will be rebuilt), so I don't need to worry about things too much). The HighlightingSelector iterates over the registered highlighters and (if they're enabled) registers a style. I cache this in a Dictionary as rebuilding these could be expensive and since they only get built at the point the user has made a manual interaction, the increased cost of doing this up front isn't noticeable.
The runtime will make a call to HighlightingSelector.SelectStyle passing in the record I care about, all I do is return the appropriate style (which was based upon the users original highlighting preferences).
public class HighlightingSelector : StyleSelector
{
private readonly Dictionary<IHighlighter, Style> styles = new Dictionary<IHighlighter, Style>();
public HighlightingSelector()
{
IHighlightingService highlightingService = ServiceLocator.Instance.Get<IHighlightingService>();
if (highlightingService == null) return;
foreach (IHighlighter highlighter in highlightingService.Highlighters)
{
if (highlighter is TypeHighlighter)
{
// No need to create a style if not enabled, should the status of a highlighter
// change, then this collection will be rebuilt.
if (highlighter.Enabled)
{
Style style = new Style(typeof (ListViewItem));
DataTrigger trigger = new DataTrigger();
trigger.Binding = new Binding("Type");
trigger.Value = (highlighter as TypeHighlighter).TypeMatch;
if (highlighter.Style != null)
{
if (highlighter.Style.Background != null)
{
trigger.Setters.Add(new Setter(Control.BackgroundProperty,
new SolidColorBrush((Color) highlighter.Style.Background)));
}
if (highlighter.Style.Foreground != null)
{
trigger.Setters.Add(new Setter(Control.ForegroundProperty,
new SolidColorBrush((Color) highlighter.Style.Foreground)));
}
}
style.Triggers.Add(trigger);
styles[highlighter] = style;
}
}
}
}
public override Style SelectStyle(object item, DependencyObject container)
{
ILogEntry entry = item as ILogEntry;
if (entry != null)
{
foreach (KeyValuePair<IHighlighter, Style> pair in styles)
{
if (pair.Key.IsMatch(entry) && pair.Key.Enabled)
{
return pair.Value;
}
}
}
return base.SelectStyle(item, container);
}
}

Problem with ItemTemplate TextBlock

i am trying to make an item template where some of the field in my stack panel can be empty. When it's empty, I would like to set the visiblility to collapsed. I tried putting triggers but it doesn't seem to work and I am not very familiar with this part of WPF
Also, I would like to change the color of the background of this item when a specific value in my binding is true. Is it the same thing?
Thanks.
Using a ViewModel is one approach to solving this kind of problem.
The if your data was stored in an Item class you would make an ItemViewModel to wrap the Item for display in your items control. The ViewModel class would implement INotifyProperty changed in order to update the display and the setters would raise the PropertyChanged event passing the appropriate property name. You can also raise property changed events for as many interrelated changed fields as necessary.
Suppose you wanted Item.Description to display in a collapsed field when Description is empty. Your ViewModel properties could look like this
public string Description
{
get { return mItem.Description; }
set { mItem.Description = value; Notify("Description"); Notify("DescriptionVisibility"); }
}
public Visibility DescriptionVisibility
{
get { return string.IsNullOrEmpty(mItem.Description) ? Visibility.Visible : Visibility.Collapsed; }
}
In the XAML bind the text property to Description and the Visibility property to DescriptionVisibility.
If you want to hide an item if it's content is null, you have to redefine the ControlTemplate of its ListBoxItem (or ListViewItem or something else depending on which item container you're using) and use triggers that target the DataContext, like:
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
However, I'd suggest that you use the Filter delegate on your CollectionView to exclude your empty items from your view directly, to avoid collapsing unused items.
For example to exclude null objects, in your code behind, use:
CollectionViewSource.GetDefaultView(yourCollection).Filter = o => o != null;

Resources