C# - how do I prevent mousewheel-scrolling in my combobox? - c#

I have a combobox and I want to prevent the user from scrolling through the items with the mousewheel.
Is there an easy way to do that?
(C#, VS2008)

Use the MouseWheel event for your ComboBox:
void comboBox1_MouseWheel(object sender, MouseEventArgs e) {
((HandledMouseEventArgs)e).Handled = true;
}
Note: you'll have to create event in code:
comboBox1.MouseWheel += new MouseEventHandler(comboBox1_MouseWheel);

For WPF, handle the PreviewMouseWheel event instead.
It would also be a good idea to consider ComboBox.IsDropDownOpen so the user can still use mouse scroll if there are a lot of items in the selection when the ComboBox is expanded.
Another thing is to apply the same behavior across the whole application.
I usually do all the above using the following code:
App.xaml
<Application.Resources>
<Style TargetType="ComboBox">
<EventSetter Event="PreviewMouseWheel" Handler="ComboBox_PreviewMouseWheel" />
</Style>
</Application.Resources>
App.xaml.cs
private void ComboBox_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
e.Handled = !((System.Windows.Controls.ComboBox)sender).IsDropDownOpen;
}

I use another solution that also works on Mono.
Goal is to prevent accidentally scrolling (that is when the user is not looking at the comboBox when using the mouse wheel). If he / she scroll outside the visible portion of comboBox , the combo box should not scroll, otherwise it should.
My solution:
Place a read only text box outside the visible portion of the screen. In form_load I placed the line: hiddenTextbox.left = -100 ;
Set the focus to this text box when the mouse leaves the combo box using mouse leave event. In comboBox1_MouseLeave I placed the line: hiddenTextbox.focus();
Handle mouseWheel event: From1.MouseWheel += Form1_MouseWheel;
textBoxHidden.MouseWheel += Form1_MouseWheel;

My Combobox's were placed inside a DataGrid [C#, WPF XAML] just like this:
<DataGrid x:Name="dgvFieldsMapping" Grid.Row="1" ItemsSource="{Binding}">
<DataGrid.Columns>
...
<DataGridTemplateColumn Width="*" Header="Destination Field" >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<ComboBox ItemsSource="{Binding Source={StaticResource CustomerDbFields}}" SelectedValue="{Binding destinationField, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
</DataGrid.Columns>
</DataGrid>
So whenever a DropDown was closed after selecting a Value, the Mousewheel would scroll that Combobox's Items and modify my Selection.
I ended up modifying my XAML to look like this:
<DataGrid x:Name="dgvFieldsMapping" Grid.Row="1" ItemsSource="{Binding}">
<DataGrid.Resources>
<Style x:Key="dgvComboBox_Loaded" TargetType="ComboBox">
<EventSetter Event="Loaded" Handler="dgvCombobox_Loaded" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
...
<DataGridTemplateColumn Width="*" Header="Destination Field" >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<ComboBox Style="{StaticResource dgvComboBox_Loaded}" ItemsSource="{Binding Source={StaticResource CustomerDbFields}}" SelectedValue="{Binding destinationField, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
</DataGrid.Columns>
</DataGrid>
And adding these lines in codebehind
public void dgvCombobox_Loaded(Object sender, RoutedEventArgs e)
{
((ComboBox)sender).DropDownClosed -= ComboBox_OnDropDownClosed;
((ComboBox)sender).DropDownClosed += new System.EventHandler(ComboBox_OnDropDownClosed);
}
void ComboBox_OnDropDownClosed(object sender, System.EventArgs e)
{
dgvFieldsMapping.Focus();
}
In this way I just move the Focus away from the ComboBox to the outer DataGrid after closing its corresponding DropDown, so I don't need to add any dummy FrameWorkElement

Related

Assign Click Event to Button in WPF ResourceDictionary

I keep my theme file (ResourceDictionary) in a separate project. I'm using the following ControlTemplate structure for the DataGrid in my ResourceDictionary:
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" Margin="10 5" />
<Button x:Name="btnFilter" Content="" FontFamily="{StaticResource FontAwesome}" FontSize="16" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Where I use DataGrid in the related project, I need to assign a click event to the button named btnFilter above, how can I do this?
Methods like VisualTree do not work very well. Since I have nearly 20 columns in Datagrid, I use horizontal scroll and VisualTree does not see the columns hidden by scroll.
What are the best practices I should follow here?
As Button.Click is a bubbling routed event, you can listen it at the control nearer to the root.
For example, assuming you name the Style as "HeaderStyle", it would be like the following.
<DataGrid ColumnHeaderStyle="{StaticResource HeaderStyle}"
Button.Click="Button_Click">
<DataGrid.Columns>
<DataGridTextColumn Header="HeaderText"/>
</DataGrid.Columns>
</DataGrid>
In case there are multiple Buttons in that control, you can filter by checking RoutedEventArgs.OriginalSource.
private void Button_Click(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is Button { Name: "btnFilter" })
{
// Handle event here.
}
}

WPF - Prevent Mousewheel scrolling in ComboBox and scroll containing Listview instead

I have a ListView whose DataTemplate contains a ComboBox. I want to satisfy the following requirements:
The ListView items are selectable
When a ListView item is Selected, the ComboBox is focused, has IsDropDownOpen=false and the user rotates the mouse wheel or moves the keyboard up/down arrow, I want to disable the ability to change selection. This should only be possible when the ComboBox has IsDropDownOpen=true.
When a ListView item is Selected and the ComboBox's IsDropDownOpen=false, I want the MouseWheel event and KeyDown event of the ComboBox to make the ListView scroll, independent of whether the ComboBox has an item selected or not and whether it has focus or doesn't have.
The following simplified XAML file and the corresponding code behind file that is based in this, show what I am trying to do
<UserControl x:Class="WpfApp.Users.UserData"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate x:Key="listViewItemTemplate">
<ComboBox Name="cb" MinWidth="150"
IsHitTestVisible="False"
PreviewMouseWheel="cb_PreviewMouseWheel"
PreviewKeyDown="cb_PreviewKeyDown">
<ComboBoxItem Name="cbi1">Item1</ComboBoxItem>
<ComboBoxItem Name="cbi2">Item2</ComboBoxItem>
</ComboBox>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType={x:Type ListViewItem}},
Path=IsSelected}" Value="True">
<Setter TargetName="cb" Property="IsHitTestVisible" Value="True"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</UserControl.Resources>
<ListView Name="lv" DockPanel.Dock="Top"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.CanContentScroll="True"
HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn
CellTemplate="{StaticResource listViewItemTemplate}"/>
</GridView>
</ListView.View>
</ListView>
private void cb_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (!((ComboBox)sender).IsDropDownOpen)
{
e.Handled = true;
MouseWheelEventArgs e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
e2.RoutedEvent = UIElement.MouseWheelEvent;
lv.RaiseEvent(e2);
}
}
private void cb_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!((ComboBox)sender).IsDropDownOpen)
{
e.Handled = true;
KeyEventArgs e2 = new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key);
e2.RoutedEvent = UIElement.KeyDownEvent;
lv.RaiseEvent(e2);
}
}
The cb_PreviewKeyDown does exactly what I want, but the cb_PreviewMouseWheel doesn't make the ListView scroll when a ListView item is Selected and the ComboBox's IsDropDownOpen=false, although ListView receives the raised MouseWheel event, as I found in my tests.
What is wrong with my solution? Is there another one that could do what I want?

Automatic checking and unchecking of checkboxes in WPF DataGrid on mouse scroll

I am developing a WPF application in which I need to send SMS to only those contacts that are checked in a DataGrid.
After checking the desired contacts in the DataGrid, I am facing problems when I scroll down/up. The checked property of the checkboxes in the DataGrid gets changed randomly.
I came across some solutions that suggested to add the following properties:
VirtualizingStackPanel.VirtualizationMode="Standard" in DataGrid
VirtualizingStackPanel.IsVirtualizing="True" in DataGrid (When I set it to False, the application becomes totally unresponsive)
UpdateSourceTrigger="PropertyChanged" in CheckBox Binding
EnableRowVirtualization="True" in DataGrid
EnableColumnVirtualization="True" in DataGrid
I tried them all, but none of them worked.
XAML:
<StackPanel Orientation="Horizontal">
<DataGrid x:Name="smsgrid" VirtualizingStackPanel.VirtualizationMode="Standard" VirtualizingStackPanel.IsVirtualizing="True" Margin="10,20,0,10" AutoGenerateColumns="False" IsReadOnly="True" CanUserResizeColumns="False" CanUserReorderColumns="False" CanUserSortColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="chkbox" Checked="chkbox_Checked" Unchecked="chkbox_Checked"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"></DataGridTextColumn>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"></DataGridTextColumn>
<DataGridTextColumn Header="Mobile no." Binding="{Binding Mobile1}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
...
<StackPanel>
I am using the StackPanel to stack a LogIn page horizontally (not pasted in the above XAML) and a List to store the mobile number if the corresponding checkbox is selected. I also noted that the values in the List remains unchanged as the values of the checked property of checkboxes changes.
It would be really nice if I get a solution for avoiding the automatic checking and unchecking of checkboxes in the DataGrid.
Thank You!
In my understanding when IsVirtualizing = true:
UI elements that are not visible are created only when you scroll to unhide them
when you unhide a new row then it is created, checked or unchecked so the event is also triggered
If you turn off virtualization the performance will drop down proportionally to number of items in your datagrid because all of them will have to be handled in UI even if they are not visible.
My workaround without turning off virtualization is to only allow execution of checkbox checked/unchecked events in the moment after you clicked on datagrid items and disable execution when you start scrolling:
In your .xaml.cs:
bool _allowChckboxEvent = true;
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (_allowChckboxEvent == false) return;
//OnChecked stuff
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
if (_allowChckboxEvent == false) return;
//OnUnchecked stuff
}
//Enable checkbox events on left button click
private void MyDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_allowChckboxEvent = true;
}
//Disable checkbox events when user starts scrolling the datagrid
private void MyDataGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
_allowChckboxEvent = false;
}
In your .xaml:
<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding MyViewModel}" AutoGenerateColumns="False" PreviewMouseLeftButtonDown="MyDataGrid_PreviewMouseLeftButtonDown" ScrollViewer.ScrollChanged="MyDataGrid_ScrollChanged">
<DataGrid.Columns>
<DataGridTemplateColumn Header="IsVisibile">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center" IsChecked="{Binding IsVisible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I hope it helps. If someone has more elegant solution I'll be happy to learn :)
EDIT: Solution 2
In the previous example you can't be sure which event will be triggered first: checked/unchecked or scrollchanged. Second solution works way better.
In your .xaml.cs:
bool _allowChckboxEvent = true;
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (_allowChckboxEvent == false) return;
//OnChecked stuff
_allowChckboxEvent = false; //checked/unchecked events are disabled right after youd stuff is done
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
if (_allowChckboxEvent == false) return;
//OnUnchecked stuff
_allowChckboxEvent = false; //checked/unchecked events are disabled after your stuff is done
}
//Enable checkbox events right before you check/uncheck checkbox
private void CheckBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_allowChckboxEvent = true;
}
In your .xaml:
<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding MyViewModel}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="IsVisibile">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center" IsChecked="{Binding IsVisible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" PreviewMouseLeftButtonDown="CheckBox_PreviewMouseLeftButtonDown" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
It's better not to disable Virtualization since your application becomes totally unresponsive.
You can directly use DataGridCheckBoxColumn, no need the heavy DataGridTemplateColumn
<DataGridCheckBoxColumn Binding="{Binding NeedSendSms}"/>
then in the ViewModel you can get all contacts with NeedSendSms is true.
If you still want to use DataGridTemplateColumn, then you need bind IsChecked property of CheckBox to avoid automatic checking and unchecking.
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding NeedSendSms}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

WPF DataGrid CancelEdit from within DataGridTemplateColumn

I'm stuck on what seems to be a basic DataGridTemplateColumn question.
I have created a WPF DataGrid (sample code below) with a DataGridTemplateColumn. Inside the DataGridTemplateColumn, I have created a UserControl for the CellEditingTemplate. Within this UserControl, I have a button (and/or want to monitor a keypress, etc) that I want to use to Cancel (or Commit) the changes to the datagrid cell.
How do I go about notifying the DataGrid to CancelEdit from within the UserControl?
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Text}"/>
<Button Content="Cancel!">
<!--
How to make this Button Cancel Editing?
Click="Cancel"
Command="{Binding CancelCommand}"
-->
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The easy way to do it is to give the grid a name (theGrid), add a click handler to the button like this...
<Button Content="Cancel!" Click="Button_Click">
...and then cancel editing in the handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
theGrid.CancelEdit();
}
The "correct" way to do it is to create a view model with a command handler, bind the button's Command property to it and then use an attached behavior that watches an event in your view model and triggers the cancel when it fires.

How to force a user to enter the data in DataGridCell

I have a dataGrid. When the first cell (which is a template column containing a textbox) of any row is focused and if user does not enter any text and tries to move to another cell or any control outside the datagrid, he should be forced to enter data in that cell before losing focus. If he does not want to enter any data in that cell, then he should press Enter or Esc as per his choice. Can anybody help me to achieve this behavior?
I can think of two ways to achieve this:
First:
You can use a DataGridTemplateColumn and set the template to a Textbox. This textbox has a PreviewLostKeyboardFocus event you can use. Setting Handled to true prevents the Textbox from losing focus.
Example:
<DataGrid CanUserAddRows="True" ItemsSource="{Binding MyItems}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="header">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding DisplayedName}" PreviewLostKeyboardFocus="OnPreviewLostKeyboardFocus" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Second
You can also add a Style to the DataGrid. This style sets the PreviewLostKeyboardFocus event of all cells:
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<EventSetter Event="PreviewLostKeyboardFocus" Handler="OnPreviewLostKeyboardFocus" />
</Style>
</DataGrid.Resources>
In the event handler you check if the cell is in the correct column and then set Handled to true:
public void OnPreviewLostKeyboardFocus(object sender, RoutedEventArgs e)
{
if ((sender as DataGridCell).Column.Header.ToString() == "The column I'm looking for")
{
e.Handled = true;
}
}

Categories