I'd like to implement a ListView that will have a list of SelectedItems as it has now,
but it will also have a separate item that is under cursor (movable with mouse/keyboard).
Item selecting will be done with special action (for example the space key).
In other words, I'd like to suppress selection on the ListView and only change FocusedItem when selection would normally occur.
I think that I should do something with ListView's FocusedItem and SelectedItems properties,
but the thing is that clicking an item selects it (also changes focus).
A solution would be to capture keydown and mouseclick events that can change selection and only change FocusedItem to the item according to the event (item under mouse pointer in mouseclick and item above/below current one in keydown up/down arrow). Then I would only add items to SelectedItems collection on my special event (space key press).
What I'm asking is if there is more ellegant approach to this problem or the solution above is as simple as it can be. Thanks
Ok, scratch this.
I needed to subclass ListView (ListViewEx);
handle keyboard selection events:
private void ListViewEx_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Down)
{
//move focus down
if (this.FocusedItem.Index + 1 < this.Items.Count)
{
this.FocusedItem = this.Items[this.FocusedItem.Index + 1];
this.FocusedItem.EnsureVisible();
e.Handled = true;
}
}
else if (e.KeyData == Keys.Up)
{
if (this.FocusedItem.Index - 1 < this.Items.Count)
{
this.FocusedItem = this.Items[this.FocusedItem.Index - 1];
this.FocusedItem.EnsureVisible();
e.Handled = true;
}
}
}
and mouse events in WndProc
this way left click/keyboard up/down moves focus and keyboard right click makes selection
protected override void WndProc(ref Message m)
{
const int WM_LBUTTONDOWN = 0x0201;
const int WM_RBUTTONDOWN = 0x204;
switch (m.Msg)
{
case WM_LBUTTONDOWN:
Point pos = this.PointToClient(Cursor.Position);
var item = GetItemAt(pos.X, pos.Y);
this.FocusedItem = item;
break;
case WM_RBUTTONDOWN:
pos = this.PointToClient(Cursor.Position);
item = GetItemAt(pos.X, pos.Y);
item.Selected = !item.Selected;
break;
default:
base.WndProc(ref m);
break;
}
}
Related
I have a simple form like this:
I open the combobox and at the time dropdown is open, I click the button. On button click I show a simple message but the message is not shown at that time.
It shows when I click it again.
The same problem for textbox. When the dropdown is open, the textbox click is not working.
Why does combobox prevent clicking other controls when it is open?
You can create an event for ComboBox DropDownClosed and with the hittestfunction, find the other control that the user has clicked.
private void ComboBox_DropDownClosed(object sender, EventArgs e)
{
Point m = Mouse.GetPosition(this);
VisualTreeHelper.HitTest(this, this.FilterCallback, this.ResultCallback, new PointHitTestParameters(m));
}
Then in the FilterCallback function after finding that control, raise the mouse down event on that control.
private HitTestFilterBehavior FilterCallback(DependencyObject o)
{
var c = o as Control;
if ((c != null) && !(o is MainWindow))
{
if (c.Focusable)
{
if (c is ComboBox)
{
(c as ComboBox).IsDropDownOpen = true;
}
else
{
var mouseDevice = Mouse.PrimaryDevice;
var mouseButtonEventArgs = new MouseButtonEventArgs(mouseDevice, 0, MouseButton.Left)
{
RoutedEvent = Mouse.MouseDownEvent,
Source = c
};
c.RaiseEvent(mouseButtonEventArgs);
}
return HitTestFilterBehavior.Stop;
}
}
return HitTestFilterBehavior.Continue;
}
private HitTestResultBehavior ResultCallback(HitTestResult r)
{
return HitTestResultBehavior.Continue;
}
The combobox is implemented the way that it captures the mouse when the dropdown is open. This is done to easyly figure out when the user clicks outside of the combobox (in fact it's a one-liner). When the user clicks outside of the combobox it releases the mouse, closes the dropdown and marks the click as handled. The last action of course stops further processing and the click is not passed to the control you thought you clicked on.
My personal opinion is this behavior has pros and cons. Microsoft decided the way it is.
I am trying to create a WPF custom slider control that acts as a scrollbar for a Listview. I'm doing this by putting the name of the listview in the Tag attribute of my custom slider and then using the slider's OnValueChange event to scroll the listview. This works great, however, when I scroll in the listview with my mousewheel the slider doesn't move. What I need is a way to attach a method to the listview's MouseWheel event when my custom slider initializes. Here is what I've tried:
Custom slider class:
public class LabeledScrollbar : Slider
{
public override void EndInit()
{
var listbox = (ListBox)this.FindName(this.Tag.ToString());
if (listbox != null)
{
listbox.MouseWheel += new MouseWheelEventHandler(this.OnMouseWheel);
}
base.EndInit();
}
protected void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
this.Value += 5;
}
protected override void OnValueChanged(double oldValue, double newValue)
{
var listBox = (ListBox)this.FindName(this.Tag.ToString());
var collection = (CollectionView)CollectionViewSource.GetDefaultView(listBox.ItemsSource);
if (newValue == this.Maximum)
{
if (VisualTreeHelper.GetChildrenCount(listBox) > 0)
{
var chrome = VisualTreeHelper.GetChild(listBox, 0);
var scrollView = (ScrollViewer)VisualTreeHelper.GetChild(chrome, 0);
scrollView.ScrollToTop();
}
}
else
{
var index = (collection.Count - 1) - (int)Math.Floor(newValue);
var selectedItem = collection.GetItemAt(index);
listBox.ScrollIntoView(selectedItem);
}
}
}
XAML:
<ListView x:Name="listViewCategories">
...
</ListView>
<local:LabeledScrollbar x:Name="categoryScrollbar" Orientation="Vertical" TickPlacement="BottomRight" Tag="listViewCategories"></local:LabeledScrollbar>
While it seems like OnMouseWheel should fire when the I scroll in the listview, it's not happening and I haven't been able to find anything else to try. Is there a way to do what I want in WPF? I know I could put a method in the code behind of my view to make the MouseScroll event of the listview move the slider, but I was hoping to encapsulate as much of the logic for the slider in the slider class as possible.
So it seems that the trick was to use PreviewMouseWheel instead of MouseWheel. For future reference here is my current class:
/// <summary>
/// This class creates a custom control that can be used as a scrollbar for a listbox that displays the current
/// group visible in the listbox on a small label next to the slider thumb.
///
/// To use it, set the Tag value to the name of the listbox the scollbar will be controlling.
/// </summary>
public class LabeledScrollbar : Slider
{
//Tracks control initialization to ensure it only gets loaded once
private bool initialized = false;
//The listview this slider will control
private ListView listView;
public LabeledScrollbar(): base()
{
this.Loaded += LabeledScrollbar_Loaded;
}
void LabeledScrollbar_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
//The tag must be set to the name of a listbox
listView = (ListView)this.FindName(this.Tag.ToString());
if (listView != null && !this.initialized)
{
//Make sure that the mouse wheel event in the linked listbox is handled
listView.PreviewMouseWheel += (s, ev) =>
{
if (ev.Delta > 0)
this.Value += 3;
else
this.Value -= 3;
};
//Move scrollbar and list to the top if the collection changes
((INotifyCollectionChanged)listView.Items).CollectionChanged += (s, ev) =>
{
this.Maximum = ((ItemCollection)listView.Items).Count - 1;
this.Value = this.Maximum;
};
//Get the max value of the slider by checking the tag value and looking up the associated listbox
this.Maximum = ((ItemCollection)listView.Items).Count - 1;
this.Value = this.Maximum;
this.initialized = true;
}
}
protected override void OnValueChanged(double oldValue, double newValue)
{
//Refresh the tickbar so that it will render for a new value
InvalidateTickbar();
//Scroll the list box to the correct location
ScrollToIndex(newValue);
}
private void ScrollToIndex(double newValue)
{
if (newValue == this.Maximum)
{
//ScrollIntoView method does not scroll to the top so
//we need to access the scrollview to move the slider to the top
if (VisualTreeHelper.GetChildrenCount(listView) > 0)
{
var chrome = VisualTreeHelper.GetChild(listView, 0);
var scrollView = (ScrollViewer)VisualTreeHelper.GetChild(chrome, 0);
scrollView.ScrollToTop();
}
}
else
{
var collection = (CollectionView)CollectionViewSource.GetDefaultView(listView.ItemsSource);
var index = (collection.Count - 1) - (int)Math.Floor(newValue);
var selectedItem = collection.GetItemAt(index);
listView.ScrollIntoView(selectedItem);
}
}
private void InvalidateTickbar()
{
//update the tickbar for the new position
if (VisualTreeHelper.GetChildrenCount(this) > 0)
{
var firstChild = VisualTreeHelper.GetChild(this, 0);
if (VisualTreeHelper.GetChildrenCount(firstChild) > 0)
{
var secondChild = (CustomTickBar)VisualTreeHelper.GetChild(firstChild, 0);
secondChild.InvalidateVisual();
}
}
}
}
I have a WPF ListBox operating in single selection mode. I am adding drag and drop to move items around. Currently the ListBox selection responds to both left button pressed and then with mouse moves with left button down. So after I wait for the MinimumVerticalDragDistance to start a drag operation, a different item could be selected. Dragging either the unselected orginal item or dragging the new selected item is confusing. Adding 'e.Handled=true' in xxx_MouseMove or xxx_PreviewMouseMove does not do anything. Any ideas on suppressing this selection due to mouse moves with left button down?
The best kludge I came up with is to cancel the ListBox's "Selection by dragging" in the IsMouseCapturedChanged event.
public partial class MainWindow : Window
{
Rect? dragSourceGestureRect;
bool busy;
public MainWindow()
{
InitializeComponent();
listBox.ItemsSource = Enumerable.Range(1, 9);
listBox.PreviewMouseLeftButtonDown += listBox_PreviewMouseLeftButtonDown;
listBox.IsMouseCapturedChanged += listBox_IsMouseCapturedChanged;
listBox.MouseMove += listBox_MouseMove;
}
void listBox_IsMouseCapturedChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (busy)
return;
if (!listBox.IsMouseCaptured)
dragSourceGestureRect = null;
else if (dragSourceGestureRect.HasValue)
{
busy = true;
{
//tell the ListBox to cancel it's "Selection by dragging"
listBox.ReleaseMouseCapture();
//Now recapture the mouse for canceling my dragging
listBox.CaptureMouse();
}
busy = false;
}
}
void listBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var center = e.GetPosition(listBox);
dragSourceGestureRect = new Rect(
center.X - SystemParameters.MinimumHorizontalDragDistance / 2,
center.Y - SystemParameters.MinimumVerticalDragDistance / 2,
SystemParameters.MinimumHorizontalDragDistance,
SystemParameters.MinimumVerticalDragDistance);
}
void listBox_MouseMove(object sender, MouseEventArgs e)
{
if (!dragSourceGestureRect.HasValue || dragSourceGestureRect.Value.Contains(e.GetPosition(listBox)))
return;
dragSourceGestureRect = null;
var data = new DataObject(DataFormats.UnicodeText, "The Data");
DragDrop.DoDragDrop(listBox, data, DragDropEffects.Copy);
e.Handled = true;
}
}
I wish to move whole form when user drags through the panel or label or any item i want.
How can i do this in VisualC++ ?
I know to do so in VB, but i am working in VisualC++.
Any help?
I don't know if there are other alternatives to this, but you could create your own handler for WM_MOUSEDOWN (don't remember it exactly). Then all you do is work out the differences between the clicked position and the controller.
Also, in order for it to move you need to handle WM_MOUSEMOVE or something, to move the form with the pointer.
You could do this by simply having a boolean tell mousemove if it should react on mouse movements or not.
Select MainForm
From Properties Window Select Events and Add MouseMove Event
The Code:
public int X_cood;
bool Mouse_Cord_Stored = false;
int MainForm_Mouse_X_Coordinates;
int MainForm_Mouse_Y_Coordinates;
Point p = new Point(ActiveForm.Location.X, ActiveForm.Location.Y);
private void MainForm_MouseMove(object sender, MouseEventArgs e)
{
switch (e.Button)
{
case (MouseButtons.Left)://To Let user move the Form1 when Left mouse button is pressed
{
switch (Mouse_Cord_Stored)
{
case false:
{
//If Mouse Coordinates are not stored, then store them.
MainForm_Mouse_X_Coordinates = e.X;
MainForm_Mouse_Y_Coordinates= e.Y;
Mouse_Cord_Stored = true;
break;
}
case true:
{
//Move the Form using ActiveForm.Location if mouse coordinates are stored
Form1.ActiveForm.Location = new Point(Form1.ActiveForm.Location.X + e.X - MainForm_Mouse_X_Coordinates
, Form1.ActiveForm.Location.Y + e.Y - MainForm_Mouse_Y_Coordinates);
break;
}
}
break;
}
default:
{
switch (Mouse_Cord_Stored)
{
case true:
{
Mouse_Cord_Stored = false;
break;
}
}
break;
}
}
I have an ItemsControl that is databound to a ObservableCollection. I have this method in the code behind which adds a new model to the list. I would then like to scroll the new item (at the bottom of the list) into view.
I think the size of the ItemsControl is not yet updated when I am querying the size, since the ActualHeight before and after the addition of the model is the same. The effect of this code is to scroll to a point slightly above the new item.
How would I know what the new ActualHeight is going to be?
Here is my code:
ViewModel.CreateNewChapter();
var height = DocumentElements.ActualHeight;
var width = DocumentElements.ActualWidth;
DocumentElements.BringIntoView(new Rect(0, height - 1, width, 1));
I think you need to call BringIntoView on the item container, not the ItemsControl itself :
var container = DocumentElements.ItemContainerGenerator.ContainerFromItem(model) as FrameworkElement;
if (container != null)
container.BringIntoView();
EDIT: actually this doesn't work, because at this point, the item container hasn't been generated yet... You could probably handle the StatusChanged event of the ItemContainerGenerator. I tried the following code :
public static class ItemsControlExtensions
{
public static void BringItemIntoView(this ItemsControl itemsControl, object item)
{
var generator = itemsControl.ItemContainerGenerator;
if (!TryBringContainerIntoView(generator, item))
{
EventHandler handler = null;
handler = (sender, e) =>
{
switch (generator.Status)
{
case GeneratorStatus.ContainersGenerated:
TryBringContainerIntoView(generator, item);
break;
case GeneratorStatus.Error:
generator.StatusChanged -= handler;
break;
case GeneratorStatus.GeneratingContainers:
return;
case GeneratorStatus.NotStarted:
return;
default:
break;
}
};
generator.StatusChanged += handler;
}
}
private static bool TryBringContainerIntoView(ItemContainerGenerator generator, object item)
{
var container = generator.ContainerFromItem(item) as FrameworkElement;
if (container != null)
{
container.BringIntoView();
return true;
}
return false;
}
}
However it doesn't work either... for some reason, ContainerFromItem still returns null after the status changes to ContainersGenerated, and I have no idea why :S
EDIT : OK, I understand now... this was because of the virtualization : the containers are generated only when they need to be displayed, so no containers are generated for hidden items. If you switch virtualization off for the ItemsControl (VirtualizingStackPanel.IsVirtualizing="False"), the solution above works fine.