WPF ListBox SelectionChanged sporadically not triggered on tablet - wpf

I'm trying to use a WPF listbox on a tablet.
In a dummy project I just made a listbox with a lot of items and when i select one this item will be showed in a textblock.
I have a selectionchanged event on the listBox
On my laptop everything works the way it should but when i run it on a tablet the selectionchanged event isn't triggered sporadically. On the screen the old selected item stays selected and the new selected one is highlighted but the item isn't shown in the textblock.
With remote debugging I have seen that the TouchDown, TouchMove and TouchUp event are all triggered, but some times the selectionChanged isn't triggered.
these things I've tried as well:
setting in Xaml inside the listbox:
ScrollViewer.PanningMode="None"
When I do this the selectionchanged event is always triggered but the user can't scroll down anymore with swiping (Which must be possible.
I think here lies the problem somewhere, but I don't have any solution yet.
Help Needed.

After a long time a solution for this problem was found.
first of we need some variables
private TouchPoint _movePoint;
private double _minimum = 0;
private double _maximum;
Me need to catch the TouchMove event of the listBox. This event triggers many times. We need get maximum and minimum Y-values of were the touch has been.
private void myListBox_TouchMove(object sender, TouchEventArgs e)
{
_movePoint := e.GetTouchPoint(myListBox);
if (_minimum.Equals(0))
{
_minimum := _movePoint.Position.Y;
_maximum := _movePoint.Position.Y;
return;
}
if (_movePoint.Position.Y < _minimum)
_minimum := _movePoint.Position.Y;
if (_movePoint.Position.Y > _maximum)
_maximum := _movePoint.Position.Y;
}
Now in the TouchUp event we look at the how far have been slided in the vertical direction. If this is not to big (in this example lower then 20), we gonna look at where the touchup event took place and look for the ListBoxItem that is on that place and set IsSelected=ture on this item.
private void myListBox_TouchUp(object sender, TouchEventArgs e)
{
var difference = _maximum - _minimum;
_maximum = 0;
_minimum=0;
if(difference < 20)
{
var touchPosition = e.GetTouchPoint(myListBox)
UIElement elem = myListBox.InputHitTest(touchPosition.Position) as UIElement;
while (elem != null)
{
if (elem == myListBox)
return;
ListBoxItem item = elem as ListBoxItem;
if (item != null)
{
item.IsSelected = true;
return;
}
elem = VisualTreeHelper.GetParent(elem) as UIElement;
}
}
}
This should work.

Related

OwnerDrawVariable ListBox selects last item when clicking on control below items

I noticed this problem in a ListBox in my application, and then determined that the example given on MSDN for ListBox.MeasureItem suffers from the same problem.
When you set a ListBox's DrawMode to OwnerDrawVariable in order to handle the MeasureItem event ( say to draw the list items with an increased height ), the control will select the last item if you click on the empty region below the last item.
I would like it to behave the way it does when DrawMode is set to OwnerDrawFixed or Normal, and not change the item selection if the user clicks on the control below the list of items.
I tried to achieve this behavior by handling the MouseDown event and found that the control selects the bottommost item before it fires the MouseDown event.
I wonder if I need to subclass ListBox, or if there is a better way to do this.
In order to see the behavior, the code sample from MSDN suffices:
https://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.measureitem(v=vs.110).aspx
You can do this by inspecting the mouse down message and not letting it process the message if the user isn't clicking on an item:
public class ListBoxEx : ListBox {
private const int WM_LBUTTONDOWN = 0x201;
protected override void WndProc(ref Message m) {
int lParam = m.LParam.ToInt32();
int wParam = m.WParam.ToInt32();
if (m.Msg == WM_LBUTTONDOWN) {
Point clickedPt = new Point();
clickedPt.X = lParam & 0x0000FFFF;
clickedPt.Y = lParam >> 16;
bool lineOK = false;
for (int i = 0; i < Items.Count; i++) {
if (GetItemRectangle(i).Contains(clickedPt)) {
lineOK = true;
}
}
if (!lineOK) {
return;
}
}
base.WndProc(ref m);
}
}
This is built-in behavior down in the Win32 control. Essentially, when you click the ListBox, a method called IndexFromPoint (you can call this yourself via the control instance) is called to determine the index of the selected item.
I tried taking full control over the process, but I couldn't find any evidence that even then you can tell the difference between clicking on an actual item or not when the control is using DrawMode.OwnerDrawVariable.
As such, it is my belief that you cannot control it. But you might dodge it:
private void ListBox1_MouseDown(object sender, MouseEventArgs e)
{
for (Int32 i = 0; i < ListBox1.Items.Count; i++)
{
var rect = ListBox1.GetItemRectangle(i);
if (rect.Contains(e.Location))
return;
}
ListBox1.SelectedIndex = -1;
}
As you noticed, the item will already be selected by the time this event fires. This simply tries to un-select it fast enough to not be noticeable. If the ListBox has a lot of items in it, you might notice it as a flicker.
This is probably the best you could do.

WPF DataGrid MouseLeftButtonDown not firing

I have a common task. Implement CheckBox checking in DataGrid by one-click. I deside make a DataGridExtended class, derived from DataGrid, and implement something like that:
XAML:
<DataGrid x:Class="DataGrid.DataGridExtended"
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="300" d:DesignWidth="300">
</DataGrid>
CODE:
public partial class DataGridExtended : System.Windows.Controls.DataGrid
{
private int _oldRowIndex;
private int _oldColumnIndex;
public DataGridExtended()
{
MouseLeftButtonUp += DataGridExtendedMouseLeftButtonUp;
MouseLeftButtonDown += DataGridExtendedMouseLeftButtonDown;
}
private void DataGridExtendedMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Если сендер реально DataGridExtended
var dataGridExt = sender as DataGridExtended;
if (dataGridExt == null)
return;
// Получаем текущую ячейку
var currentCell = dataGridExt.CurrentCell;
_oldRowIndex = dataGridExt.SelectedIndex;
_oldColumnIndex = dataGridExt.CurrentColumn.DisplayIndex;
}
private void DataGridExtendedMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// Если сендер реально DataGridExtended
var dataGridExt = sender as DataGridExtended;
if (dataGridExt == null)
return;
var rowIndex = dataGridExt.SelectedIndex;
var columnIndex = dataGridExt.CurrentColumn.DisplayIndex;
// Получаем текущую ячейку
var currentCell = dataGridExt.CurrentCell;
//if (_oldRowIndex != rowIndex || _oldColumnIndex != columnIndex)
// return;
// Получаем текущую колонку
var currentColumn = currentCell.Column;
// Получаем контент текущей ячейки
var cellContent = currentColumn.GetCellContent(currentCell.Item);
// Если кликнули по чекбоксу
var checkBox = cellContent as CheckBox;
if (checkBox == null)
return;
// Ставием его в фокус
checkBox.Focus();
// Меняем чек на противоположный
checkBox.IsChecked = !checkBox.IsChecked;
// Получаем выражение привязки для чекбокса
var bindingExpression = checkBox.GetBindingExpression(ToggleButton.IsCheckedProperty);
// Если привязка есть - обновляем ее
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
}
DataGridExtendedMouseLeftButtonUp handler works fine, but DataGridExtendedMouseLeftButtonDown doesn't firing. And that is the problem.
Without DataGridExtendedMouseLeftButtonDown invoking, checking behaviour is not what I want. Namely, checking is working even I move cursor out from grid :E
Trying to use PreviewMouseLeftButtonDown instead MouseLeftButtonDown give wrong effect :(
So, how can I solve my problem? Don't offer use different approaches to implement one-click checking plz :) Like using XAML-style for example...
In WPF, we often get situations where a particular Click handler appears not to work. The reason for this is usually because a control (or our own code) is handling that event and setting e.Handled = true;, which stops the event from being passed any further. In these situations, it is generally accepted that you should try to access the event before this happens and so we turn to the matching/related Preview event.
In your situation, I would recommend that you use the PreviewMouseLeftButtonDown event. You said that something is not initialized by then, but that doesn't make any sense to me. You said that you need to save the previous value, but you could do that just from your DataGridExtendedMouseLeftButtonUp event handler.
When the user releases the mouse button the first time, then you have their new value. Save this in a variable. When the user releases the mouse button the next and each subsequent time, then save their previous value from the variable as the old value and then read their new value into the variable.
Try MouseDown event and then figure out right or left

How can I make WPF ScrollViewer middle-click-scroll?

Clicking the middle mouse button (aka: mouse wheel) and then moving the mouse down slightly lets users scroll in IE, and most Windows apps. This behavior appears to be missing in WPF controls by default? Is there a setting, a workaround, or something obvious that I'm missing?
I have found how to achieve this using 3 mouse events (MouseDown, MouseUp, MouseMove). Their handlers are attached to the ScrollViewer element in the xaml below:
<Grid>
<ScrollViewer MouseDown="ScrollViewer_MouseDown" MouseUp="ScrollViewer_MouseUp" MouseMove="ScrollViewer_MouseMove">
<StackPanel x:Name="dynamicLongStackPanel">
</StackPanel>
</ScrollViewer>
<Canvas x:Name="topLayer" IsHitTestVisible="False" />
</Grid>
It would be better to write a behaviour instead of events in code-behind, but not everyone has the necessary library, and also I don't know how to connect it with the Canvas.
The event handlers:
private bool isMoving = false; //False - ignore mouse movements and don't scroll
private bool isDeferredMovingStarted = false; //True - Mouse down -> Mouse up without moving -> Move; False - Mouse down -> Move
private Point? startPosition = null;
private double slowdown = 200; //The number 200 is found from experiments, it should be corrected
private void ScrollViewer_MouseDown(object sender, MouseButtonEventArgs e)
{
if (this.isMoving == true) //Moving with a released wheel and pressing a button
this.CancelScrolling();
else if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Pressed)
{
if (this.isMoving == false) //Pressing a wheel the first time
{
this.isMoving = true;
this.startPosition = e.GetPosition(sender as IInputElement);
this.isDeferredMovingStarted = true; //the default value is true until the opposite value is set
this.AddScrollSign(e.GetPosition(this.topLayer).X, e.GetPosition(this.topLayer).Y);
}
}
}
private void ScrollViewer_MouseUp(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Released && this.isDeferredMovingStarted != true)
this.CancelScrolling();
}
private void CancelScrolling()
{
this.isMoving = false;
this.startPosition = null;
this.isDeferredMovingStarted = false;
this.RemoveScrollSign();
}
private void ScrollViewer_MouseMove(object sender, MouseEventArgs e)
{
var sv = sender as ScrollViewer;
if (this.isMoving && sv != null)
{
this.isDeferredMovingStarted = false; //standard scrolling (Mouse down -> Move)
var currentPosition = e.GetPosition(sv);
var offset = currentPosition - startPosition.Value;
offset.Y /= slowdown;
offset.X /= slowdown;
//if(Math.Abs(offset.Y) > 25.0/slowdown) //Some kind of a dead space, uncomment if it is neccessary
sv.ScrollToVerticalOffset(sv.VerticalOffset + offset.Y);
sv.ScrollToHorizontalOffset(sv.HorizontalOffset + offset.X);
}
}
If to remove the method calls AddScrollSign and RemoveScrollSign this example will work. But I have extended it with 2 methods which set scroll icon:
private void AddScrollSign(double x, double y)
{
int size = 50;
var img = new BitmapImage(new Uri(#"d:\middle_button_scroll.png"));
var adorner = new Image() { Source = img, Width = size, Height = size };
//var adorner = new Ellipse { Stroke = Brushes.Red, StrokeThickness = 2.0, Width = 20, Height = 20 };
this.topLayer.Children.Add(adorner);
Canvas.SetLeft(adorner, x - size / 2);
Canvas.SetTop(adorner, y - size / 2);
}
private void RemoveScrollSign()
{
this.topLayer.Children.Clear();
}
Example of icons:
And one last remark: there are some problems with the way Press -> Immediately Release -> Move. It is supposed to cancel scrolling if a user clicks the mouse left button, or any key of keyboard, or the application looses focus. There are many events and I don't have time to handle them all.
But standard way Press -> Move -> Release works without problems.
vorrtex posted a nice solution, please upvote him!
I do have some suggestions for his solution though, that are too lengthy to fit them all in comments, that's why I post a separate answer and direct it to him!
You mention problems with Press->Release->Move. You should use MouseCapturing to get the MouseEvents even when the Mouse is not over the ScrollViewer anymore. I have not tested it, but I guess your solution also fails in Press->Move->Move outside of ScrollViewer->Release, Mousecapturing will take care of that too.
Also you mention using a Behavior. I'd rather suggest an attached behavior that doesn't need extra dependencies.
You should definately not use an extra Canvas but do this in an Adorner.
The ScrollViewer itsself hosts a ScrollContentPresenter that defines an AdornerLayer. You should insert the Adorner there. This removes the need for any further dependency and also keeps the attached behavior as simple as IsMiddleScrollable="true".

Windows Phone 7 - Deselecting ListBoxItem in nested ListBoxes

I have a ListBox with dates.
Each ListBoxItem (date) have another ListBox with that date's events.
When I select an event it gets highlighted (SelectedIndex/SelectedItem) and I navigate to another Pivot. This works fine.
My problem is that every ListBox has it's own SelectedItem. I want to clear the SelectedItem from each ListBox, but I cannot get it to work!
Here's my try:
//Store a reference to the latest selected ListBox
public ListBox SelectedListBox { get; set; }
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
ListBox lstBox = ((ListBox)sender);
//This row breaks the SECOND time!!
var episode = (Episode)lstBox.SelectedItem;
episodeShowName.Text = episode.Show; //Do some code
episodeTitle.Text = episode.Name; //Do some code
episodeNumber.Text = episode.Number; //Do some code
episodeSummary.Text = episode.Summary; //Do some code
resetListBox(lstBox); //Do the reset !
pivot1.SelectedIndex = 1;
}
private void resetListBox(ListBox lstBox)
{
if (SelectedListBox != null)
SelectedListBox.SelectedIndex = -1;
//If I remove this line, the code doesn't break anymore
SelectedListBox = lstBox; //Set the current ListBox as reference
}
var episode is null the second time. How come?
I found the problem!
private void resetListBox(ListBox lstBox)
{
if (SelectedListBox != null)
SelectedListBox.SelectedIndex = -1;
//If I remove this line, the code doesn't break anymore
SelectedListBox = lstBox; //Set the current ListBox as reference
}
When I set the previous selected ListBox's SelectedIndex to -1, the SelectionChangedHandler event gets triggered again (of course) and screws up ! :D
Easy fix:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
ListBox lstBox = ((ListBox)sender);
if (lstBox.SelectedIndex < 0)
return;

How to detect double click on list view scroll bar?

I have two list view on WPF. The first listview is loaded with a Datatable. When double clicking on one item from the first listview, the selectedItem is moved to the second listview.
The problem arises when appears an scroll bar in the first list view due to a lot of elements loaded from the DataTable. If a select one item and double click on the scroll bar down arrow, MouseDoubleClick event is launched and the selected item is moved to the second listview.
How I can detect the double click on the scroll bar to prevent this?
Thanks a lot!
I tested the above code which was very helpful, but found the following to be more stable, as sometimes the source gets reported as GridViewRowPresenter when in fact you are double clicking an item.
var src = VisualTreeHelper.GetParent((DependencyObject)e.OriginalSource);
var srcType = src.GetType();
if (srcType == typeof(ListViewItem) || srcType == typeof(GridViewRowPresenter))
{
// Your logic here
}
Try this in you MouseDoubleClick event on the first Listview:
DependencyObject src = VisualTreeHelper.GetParent((DependencyObject)e.OriginalSource);
if(src is Control && src.GetType() == typeof(ListViewItem))
{
// Your logic here
}
Based on this.
I am using this in various projects and it solves the problem you are facing.
private void ListBox_OnMouseDoubleClick(object pSender, MouseButtonEventArgs pE)
{
FrameworkElement originalSource = pE.OriginalSource as FrameworkElement;
FrameworkElement source = pE.Source as FrameworkElement;
if (originalSource.DataContext != source.DataContext)
{
logic here
}
}
When you have the DataContext you can easy see if the sender is an item or the main listbox
I've got the final solution:
private void ListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var originalSource = (DependencyObject)e.OriginalSource;
while ((originalSource != null) && !(originalSource is ListViewItem)) originalSource = VisualTreeHelper.GetParent(originalSource);
if (originalSource == null) return;
}
it works for me.

Resources