Highlight multiple items in listbox - c#

Is there any way to achieve something like office undo drop down (image bellow) ?
I mean, i want to highlight previous item when user mouse over item other than first ?
I tried some control from FluentRibbon but so far without luck..

In most cases designing a control like this is done in Blend. However, if you don't know how to use Blend, you can still achieve the similar results with just XAML and the code-behind, but you have to do more work.
We start by creating a class called CustomListBoxItem which inherits from ListBoxItem. Then, we define a dependency property, which is used for highlighting items in the listbox:
public class CustomListBoxItem : ListBoxItem
{
public static readonly DependencyProperty IsVirtuallySelectedProperty =
DependencyProperty.Register("IsVirtuallySelected", typeof(bool),
typeof(CustomListBoxItem),
new PropertyMetadata(false));
public CustomListBoxItem() : base()
{ }
public bool IsVirtuallySelected
{
get { return (bool)GetValue(IsVirtuallySelectedProperty); }
set { SetValue(IsVirtuallySelectedProperty, value); }
}
}
Then we add a listbox and define a style for it in XAML:
<ListBox Name="listBox" MouseMove="listBox_MouseMove" SelectionChanged="listBox_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type local:CustomListBoxItem}">
<Style.Triggers>
<Trigger Property="IsVirtuallySelected" Value="true">
<Setter Property="Background" Value="SkyBlue"/>
</Trigger>
<Trigger Property="IsVirtuallySelected" Value="false">
<Setter Property="Background" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
where local is the namespace in which CustomListBoxItem is defined. This is all we need for the XAML part, the real magic happens in the code behind.
The listBox_MouseMove event handler looks like this:
private void listBox_MouseMove(object sender, MouseEventArgs e)
{
bool itemFound = false;
for (int i = 0; i < listBox.Items.Count; i++)
{
var currentItem = listBox.ItemContainerGenerator.ContainerFromIndex(i) as CustomListBoxItem;
if (currentItem == null)
continue;
// Check whether the cursor is on an item or not.
if (IsMouseOverItem(currentItem, e.GetPosition((IInputElement)currentItem)))
{
// Unselect all items before selecting the new group
listBox.Items.Cast<CustomListBoxItem>().ToList().ForEach(x => x.IsVirtuallySelected = false);
// Select the current item and the ones above it
for (int j = 0; j <= listBox.Items.IndexOf(currentItem); j++)
{
((CustomListBoxItem)listBox.Items[j]).IsVirtuallySelected = true;
}
itemFound = true;
break;
}
}
// If the item wasn't found for the mouse point, it means the pointer is not over any item, so unselect all.
if (!itemFound)
{
listBox.Items.Cast<CustomListBoxItem>().ToList().ForEach(x => x.IsVirtuallySelected = false);
}
}
And the IsMouseOverItem helper method, which is used to determine if the cursor is on an item, is defined like this:
private bool IsMouseOverItem(Visual item, Point mouseOverPoint)
{
Rect currentDescendantBounds = VisualTreeHelper.GetDescendantBounds(item);
return currentDescendantBounds.Contains(mouseOverPoint);
}
And finally the listBox_SelectedChanged event handler which acts as the click handler for the ListBox:
private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Get all the virtually selected items
List<CustomListBoxItem> selectedItems =
listBox.Items.Cast<CustomListBoxItem>().Where(x => x.IsVirtuallySelected).ToList();
if (selectedItems == null || !selectedItems.Any())
return;
// Do something with the selected items
DoCoolStuffWithSelectedItems();
// Unselsect all.
listBox.Items.Cast<CustomListBoxItem>().ToList().ForEach(x => x.IsVirtuallySelected = false);
listBox.UnselectAll();
}
And boom, we're done. We can now add some items to the ListBox in the class constructor:
public MainWindow()
{
InitializeComponent();
listBox.Items.Add(new CustomListBoxItem { Content = "hello world!" });
listBox.Items.Add(new CustomListBoxItem { Content = "wpf is cool" });
listBox.Items.Add(new CustomListBoxItem { Content = "today is tuesday..." });
listBox.Items.Add(new CustomListBoxItem { Content = "I like coffee" });
}
Note that I used a random color as the highlight color in XAML. Feel free to change it and give it a try.

Guess you need something like this:
<ControlTemplate TargetType="ListBoxItem">
<TextBlock Text="{Binding LastOperation}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger>
<DataTrigger.Binding>
<MultiBinding>
<Binding Path="MouseOverIndex"/>
<Binding Path="CurrentIndex"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Gold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</ControlTemplate>

Related

How to properly color the last rows in a datagrid [duplicate]

Can you guy tell me a great way of coloring the last row in a datagrid in WPF, I have to color the first and last rows, I found a way to do the first one by doing the code below, but I need a way to do the last row.
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Mode=PreviousData}}"
Value="{x:Null}">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
This is a tough one.
You may use the alternation index with a multi-binding on the ItemsSource and the AlternationCount, a IMultiValueConverter, and finally fail because the alternation index may not start from 0. (also, you loose the benefit of an AlternationCount of 2).
You may use code behind and hack the DataGrid to display the hard coded color you want. But that is not MVVM.
You may put a special boolean on your view model to mark those items as special. But this feels to be the wrong place.
...
My approach is to inherit the DataGrid object in a class that will keep up-to-date a ‘IsAnExtremity’ attached property (a pretty touchy pattern) on it's DataGridRow:
public class DataGridEx : DataGrid
{
private static readonly DependencyPropertyKey IsAnExtremityPropertyKey =
DependencyProperty.RegisterAttachedReadOnly(
"IsAnExtremity",
typeof(bool),
typeof(DataGridEx),
new FrameworkPropertyMetadata(defaultValue: false,
flags: FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty IsAnExtremityProperty = IsAnExtremityPropertyKey.DependencyProperty;
public static bool GetIsAnExtremity(DataGridRow dataGridRow)
{
return (bool)dataGridRow.GetValue(IsAnExtremityProperty);
}
private static void SetIsAnExtremity(DataGridRow dataGridRow, bool value)
{
dataGridRow.SetValue(IsAnExtremityPropertyKey, value);
}
private IReadOnlyList<DataGridRow> _extremities = Array.Empty<DataGridRow>();
protected override void OnLoadingRow(DataGridRowEventArgs e)
{
base.OnLoadingRow(e);
UpdateExtremities();
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
UpdateExtremities();
}
private void UpdateExtremities()
{
// Current extremities
var extremities = new[]
{
ItemContainerGenerator.ContainerFromIndex(0),
ItemContainerGenerator.ContainerFromIndex(Items.Count - 1)
}.OfType<DataGridRow>()
.Distinct()
.ToArray();
// Remove the flag from old extremities (if any).
foreach (var oldExtremityContainer in _extremities.Except(extremities))
{
SetIsAnExtremity(oldExtremityContainer, false);
}
// Ensure the flag for new extremities.
foreach (var extremityContainer in extremities)
{
SetIsAnExtremity(extremityContainer, true);
}
_extremities = extremities;
}
}
Then you can use this attached property almost like any other properties in the xaml:
<controls:DataGridEx ItemsSource="{Binding Path=Items}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="LightBlue" />
<Style.Triggers>
<Trigger Property="controls:DataGridEx.IsAnExtremity" Value="True">
<Setter Property="Background" Value="Blue" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</controls:DataGridEx>
A working implementation is available here.

How can I make a ListView with custom color with both mouse and keyboard navigation in WPF?

I work on WPF/C# application. A part of this application is a WPF ListView that support navigation by mouse and keyboard. How can I make this work with a custom background colors for displaying that and list item is active/selcted? I would like a solution based on XAML with mininal C#.
I have tried making "manual" styling from code behind, by adding setters to the item style. It seems like this strategy is messing things up. I end up getting only default WPF styling
I have searched a lot on the internet and stack overflow but could not find anything specific about styling WPF listview with keyboard navigation
XAML
<Window.Resources>
<!-- This styling does not work. Should it be changed? -->
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Brown"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListView x:Name="listView"
PreviewKeyDown="listView_PreviewKeyDown"
SelectionChanged="listView_SelectionChanged">
<ListViewItem>A</ListViewItem>
<ListViewItem>B</ListViewItem>
<ListViewItem>C</ListViewItem>
</ListView>
</Grid>
C#
public MainWindow()
{
InitializeComponent();
KeyboardNavigation.SetTabNavigation(listView, KeyboardNavigationMode.Cycle);
}
public void listView_PreviewKeyDown(object sender, KeyEventArgs e)
{
for (int i = 0; i < 3; i++)
{
var item = GetListViewItemOnIndex(listView, i);
if (item.IsFocused) { /* Set selected style? */ }
else { /* Set transparent style? */ }
}
}
private void listView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
for (int i = 0; i < 3; i++)
{
var item = GetListViewItemOnIndex(listView, i);
if (item.IsFocused) { /* Set selected style? */ }
else { /* Set transparent style? */ }
}
}
private ListViewItem GetListViewItemOnIndex(ListView listView, int index)
{
return listView.ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
}

Edit DataGrid Cell on mouse over

I have a DataGrid which has plenty rows and columns,
I want to get the cell ready for editing when the user focus the mouse on it (IsMouseOver).
So far, all I found is this
<Window.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="green"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
I am able to set a property for the cell when the mouse is over it.
But how to launch an event when the mouse is over?
I would add an EventSetter in a Style like this :
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="MouseEnter" Handler="EventSetter_OnHandler"/>
</Style>
</DataGrid.Resources>
Here is the handler:
private void EventSetter_OnHandler(object sender, MouseEventArgs e)
{
DataGridCell dgc = sender as DataGridCell;
TextBox tb = Utils.GetChildOfType<TextBox>(dgc);
tb.Focus();
}
In fact you said you want to edit something. In my case, there is a TextBox and i reach it with this helper:
public static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
After reaching it, a simple Focus() will do the final job.
You can launch a mouseover Event in Xaml Like this From where you have your DataGridCell
<DataGridCell MouseEnter="DataGridCell_MouseEnter"/>

get row content by field from DataGridRow

I have a DataGrid with several columns in my application.. I need to use different row foreground colour in case of some certain field content. I use following callback on LoadingRowevent:
void userGrid_LoadingRow(object sender, DataGridRowEventArgs e){
if (e.Row.Item != null){
// check some row's field value
...
// modify row forecolor
e.Row.Foreground = new SolidColorBrush(Colors.Red);
...
}
}
But how to get value of some certain row's field by it's name or index?
void userGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
var dataGrid = (DataGrid)sender;
IEnumrable<DataGridRow> rows = dataGrid.Items.Where(r => (r.DataContext as YourItemsSourceEntity).SomeProperty == yourCondition)
}
Alternatively i would add a condition to your ItemsSource .
public class YourItemsSourceEntity
{
public bool IsSomething { get; }
}
xaml :
<DataGrid ItemsSource="{Binding Items}">
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSomething}" Value="True">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.ItemContainerStyle>
</DataGrid>
as to the comment below : Is this what you ment ?
void userGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
var item = e.Row.DataContext as (YourItemsSourceEntity);
var id = item.ID ;
}

WPF: Setting IsSelected for ListBox when TextBox has focus, without losing selection on LostFocus

I've a ListBox with ListBoxItems with a template so they contain TextBoxes
When the TextBox gets focused I want the ListBoxItem to be selected. One solution I've found looks like this:
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
This works great, but when the TextBox loses focus so does the selection.
Is there a way to prevent this from happening?
Best solution I've found to do this with no code behinde is this:
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<EventTrigger RoutedEvent="PreviewGotKeyboardFocus">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetProperty="(ListBoxItem.IsSelected)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
You can also keep focus on the text box, but only have one ListBoxItem selected at any given time, with code behind.
In the ListBox XAML:
<ListBox
PreviewLostKeyboardFocus="CheckFocus">
</ListBox>
Then, in the CheckFocus() method in code-behind:
/* Cause the original ListBoxItem to lose focus
* only if another ListBoxItem is being selected.
* If a different element type is selected, the
* original ListBoxItem will keep focus.
*/
private void CheckFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// check if focus is moving from a ListBoxItem, to a ListBoxItem
if (e.OldFocus.GetType().Name == "ListBoxItem" && e.NewFocus.GetType().Name == "ListBoxItem")
{
// if so, cause the original ListBoxItem to loose focus
(e.OldFocus as ListBoxItem).IsSelected = false;
}
}
From the list of suggested solutions nothing helped me to resolve the same issue.
This is the custom solution I made:
1). Create Behavior (class that holds attached properties) that is going to enforce the focus:
public class TextBoxBehaviors
{
public static bool GetEnforceFocus(DependencyObject obj)
{
return (bool)obj.GetValue(EnforceFocusProperty);
}
public static void SetEnforceFocus(DependencyObject obj, bool value)
{
obj.SetValue(EnforceFocusProperty, value);
}
// Using a DependencyProperty as the backing store for EnforceFocus. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnforceFocusProperty =
DependencyProperty.RegisterAttached("EnforceFocus", typeof(bool), typeof(TextBoxBehaviors), new PropertyMetadata(false,
(o, e) =>
{
bool newValue = (bool)e.NewValue;
if (!newValue) return;
TextBox tb = o as TextBox;
if (tb == null)
{
MessageBox.Show("Target object should be typeof TextBox only. Execution has been seased", "TextBoxBehaviors warning",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
tb.TextChanged += OnTextChanged;
}));
private static void OnTextChanged(object o, TextChangedEventArgs e)
{
TextBox tb = o as TextBox;
tb.Focus();
/* You have to place your caret at the end of your text manually, because each focus repalce your caret at the beging of text.*/
tb.CaretIndex = tb.Text.Length;
}
}
2). Use this behavior in your XAML:
<DataTemplate x:Key="MyDataTemplate">
<TextBox behaviors:TextBoxBehaviors.EnforceFocus="True"
Text="{Binding Path=MyProperty, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>

Categories

Resources