How to disable possibility to select particular child of InkCanvas? - c#

I'm using InkCanvas and I'm trying to disable selection for its particular child. InkCanvas children collection consists of Eliipse and Path objects and I want to disable possibility to select all of the Path objects. I was trying to achieve it by checking if mouse hit specific object in PreviewMouseLeftButtonDown event handler:
private void myCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point downPosition = e.GetPosition(myCanvas);
UIElement element = myPath;
if (myCanvas.InputHitTest(downPosition) == element) e.Handled = true;
}
Now I know that it won't work because InputHitTest function returns InkCanvas selection adorner, if I click on the object one more time, when it is already selected above function works (at least InputHitTest is returning object that interests me). Do you have any idea how to make this work?

So far what I can see is that you want particular child of inkcanvas not be selected when clicking on canvas
so assuming you have a path called myPath
private void InkCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point downPosition = e.GetPosition(myPath);
if (myPath.InputHitTest(downPosition) == myPath)
e.Handled = true;
}
so directly do hit test on the path and see if it return itself then you can determine a click on the path and then by setting e.Handled = true you make the event consumed hence the element will not be selected.
alternate approach will be testing with null, this will have the same behavior as above, so depends on choice
if (myPath.InputHitTest(downPosition) != null)
e.Handled = true;
I propose the above solution based on assumption, you please correct me if this is not what you are looking for.

Related

C# Drag and Drop From listBox

I'm trying to build a simple interface that allows users to drop files into a listBox to add them to a process, and to drag them out to remove them. Everything is working fine, but I'd like to add one feature to make it just a tad more sophisticated.
Right now, I have the removal of the item tied to the DragLeave event, which means that as soon as the mouse leaves the box, the item is removed. But I'd like for users to be able to change their minds. In other words, if they realize they're dragging the wrong file out, I'd like them to be able to move the mouse back into the listBox and release the mouse to cancel the action. I'm thinking that means I need to be able to capture the MouseUp event instead of the DragLeave event. But that hasn't been successful so far.
Below is the code I'm currently using for removing files dragged out. How can I modify to keep the files from being removed form the list until the user lets the mouse button go?
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (listBox1.Items.Count == 0)
{
return;
}
int index = listBox1.IndexFromPoint(e.X, e.Y);
string s = listBox1.Items[index].ToString();
DragDropEffects dde1 = DoDragDrop(s, DragDropEffects.All);
}
private void listBox1_DragLeave(object sender, EventArgs e)
{
ListBox lb = sender as ListBox;
lb.Items.Remove(lb.SelectedItem);
}
Edit 2013/05/16
The comments and answers so far have been useful, but I realize my question isn't clear enough. In this case, I'm displaying a dialog separate from the parent form that is basically as big as the listBox. When someone drags a file out of the list, they're dragging it off the form completely. Have I backed myself into a corner by doing this? I recognize I'm making it harder than it has to be, but I'd still like to see how it would work if it's possible.
Here's a fairly quick hack approach to gaining the functionality you want:
public object lb_item = null;
private void listBox1_DragLeave(object sender, EventArgs e)
{
ListBox lb = sender as ListBox;
lb_item = lb.SelectedItem;
lb.Items.Remove(lb.SelectedItem);
}
private void listBox1_DragEnter(object sender, DragEventArgs e)
{
if (lb_item != null)
{
listBox1.Items.Add(lb_item);
lb_item = null;
}
}
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
lb_item = null;
if (listBox1.Items.Count == 0)
{
return;
}
int index = listBox1.IndexFromPoint(e.X, e.Y);
string s = listBox1.Items[index].ToString();
DragDropEffects dde1 = DoDragDrop(s, DragDropEffects.All);
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
lb_item = null;
}
Every time the user drags an item out of the box, it's saved temporarily until the user drops it somewhere else or mouses down on a new item in the list.
Note the important part of this is detecting when and where the user let's go of that mouse, which is the rationale behind handling the DragDrop event of Form1, the parent of listBox1.
Depending on the sophistication and density of the rest of your layout, where you handle DragDrop could be much different for you. This is why it's kind of "hacky", but it's also quite simple. It shouldn't matter, though, where or how many times you null lb_item since it pertains only to that specific ListBox.
I suppose another way to do it would be to track the user's mouse states and act accordingly, which may be more appropriate for you if it's inconceivable to handle a lot of DragDrop stuff.
EDIT: If you wanted to be REAL thorough, you could enumerate through every control of the base form using foreach and programmatically append a handler for the DragDrop event to that control, then remove it when done... but that may be getting a little nutty. I'm sure someone has a better approach.

WPF equivalent of Control.CursorChanged event

I am trying to change my mouse cursor at certain point when I'm dragging my mouse around in a wpf listview. However, when I set my mouse, it quickly gets overridden by something else, and get changed back to the drag cursor.
I am not sure where the cursor change comes from, it is certainly not from my code, so it has to be system. If it is system, then I have to intercept the event for cursor change, and handle the event in order for the cursor to show what I want right?
So is there a WPF equivalent of this Control.CursorChanged event? Or perhaps there's some other way to approach this problem?
Edit:
here's part of my code
private void SetDragCursor()
{
if (_badDragLoc)
{
Mouse.OverrideCursor = Cursors.No;
}
else
{
Mouse.OverrideCursor = Cursors.Arrow;
}
}
private void listView_DragOver(object sender, DragEventArgs e)
{
if (at a bad drag location)
{
_badDragLoc = true;
SetDragCursor();
}
}
I also have a drag leave event handler, in which I also have the SetDragCursor() method as well. When I step by step go through each line of code in debugger, the mouse turned into the drag cursor from the no cursor right after it enters the drag leave handler. Which is why I think it has to be the system.
If it indeed is the system, then if I can capture the event firing, I can then handle those event myself and not let it bubble through.
Thank you!
Just does not work like that, the way to set the cursor during a DragOver event is the following:
void listView__DragOver(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("Images"))
{
e.Effects = DragDropEffects.None;
e.Handled = true;
}
}
depending on the value of DragDropEffects enum you assign to e.Effects the mouse will change cursor.
do not call Mouse.OverrideCursor because is not the right way.

MouseDoubleClick events don't bubble

My scenario, simplified: I have a ListView containing rows of Employees, and in each Employee row, there are buttons "Increase" and "Decrease" adjusting his salary.
Pretend that in my program, double-clicking an Employee row means "fire this person".
The problem is that while I'm clicking "Increase" rapidly, this triggers a double click event on the ListViewItem. Naturally, I don't want to fire people when I'm just increasing their salary.
According to how all other events work, I expect to be able to solve this by setting Handled=true on the event. This, however, doesn't work. It appears to me that WPF generates two separate, completely unlinked, double click events.
The following is a minimal example to reproduce my issue. The visible components:
<ListView>
<ListViewItem MouseDoubleClick="ListViewItem_MouseDoubleClick">
<Button MouseDoubleClick="Button_MouseDoubleClick"/>
</ListViewItem>
</ListView>
And the handler code:
private void Button_MouseDoubleClick(object s, MouseButtonEventArgs e) {
if (!e.Handled) MessageBox.Show("Button got unhandled doubleclick.");
e.Handled = true;
}
private void ListViewItem_MouseDoubleClick(object s, MouseButtonEventArgs e) {
if (!e.Handled) MessageBox.Show("ListViewItem got unhandled doubleclick.");
e.Handled = true;
}
After firing up this program and double-clicking the listed button, both messageboxes show up in sequence. (Also, the button is stuck in the down position after this.)
As a "fix" I can, on the ListViewItem handler, inspect the visual tree attached to the event and check that "there is a button there somewhere" and thus discard the event, but this is a last resort. I want to at least understand the issue before coding such a kludge.
Does anyone know why WPF does this, and an elegant idiomatic way to avoid the problem?
I think you'll find that the MouseDoubleClick event is an abstraction on top of the MouseDown event. That is, if two MouseDown events occur in quick enough succession, the MouseDoubleClick event will also be raised. Both the Button and ListViewItem appear to have this logic, so that explains why you're seeing two distinct MouseDoubleClick events.
As per MSDN:
Although this routed event seems to
follow a bubbling route through an
element tree, it actually is a direct
routed event that is raised along the
element tree by each UIElement. If you
set the Handled property to true in a
MouseDoubleClick event handler,
subsequent MouseDoubleClick events
along the route will occur with
Handled set to false.
You could try handling MouseDown on the Button and setting that to handled so that it doesn't propagate to the ListViewItem.
Wish I could verify this myself but I'm .NET-less at the moment.
The MSDN documentation for the MouseDoubleClick does give a suggestion on how to keep the MouseDoubleClick event from bubbling up:
Control authors who want to handle
mouse double clicks should use the
MouseLeftButtonDown event when
ClickCount is equal to two. This will
cause the state of Handled to
propagate appropriately in the case
where another element in the element
tree handles the event.
So you could hanlde the MouseLeftButtonDown event and set hanged to true if ClickCount is two. But this fails on Buttons because they already handle the MouseLeftButtonDown and don't raise that event.
But there is still the PreviewMouseLeftButtonDown event. Use that on your buttons to set handled to true when ClickCount equals two as below:
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
if (e.ClickCount == 2)
e.Handled = true;
}
Since there have been no definite answers to this question, this is the workaround I ended up using:
protected override void ListViewItem_MouseDoubleClick(MouseButtonEventArgs e) {
var originalSource = e.OriginalSource as System.Windows.Media.Visual;
if (originalSource.IsDescendantOf(this)) {
// Test for IsDescendantOf because other event handlers can have changed
// the visual tree such that the actually clicked original source
// component is no longer in the tree.
// You may want to handle the "not" case differently, but for my
// application's UI, this makes sense.
for (System.Windows.DependencyObject depObj = originalSource;
depObj != this;
depObj = System.Windows.Media.VisualTreeHelper.GetParent(depObj))
{
if (depObj is System.Windows.Controls.Primitives.ButtonBase) return;
}
}
MessageBox.Show("ListViewItem doubleclicked.");
}
Class names are here unnecessarily typed with full namespaces for documentation purposes.
Well it may not be elegant or idiomatic, but you might like it better than your current workaround:
int handledTimestamp = 0;
private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Timestamp != handledTimestamp)
{
System.Diagnostics.Debug.WriteLine("ListView at " + e.Timestamp);
handledTimestamp = e.Timestamp;
}
e.Handled = true;
}
private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Timestamp != handledTimestamp)
{
System.Diagnostics.Debug.WriteLine("Button at " + e.Timestamp);
handledTimestamp = e.Timestamp;
}
e.Handled = true;
}
The weird thing is this doesn't work if you don't set e.Handled = true. If you don't set e.Handled and put a breakpoint or a Sleep into the button's handler, you will see the delay in the ListView's handler. (Even without an explicit delay there will still be some small delay, enough to break it.) But once you set e.Handled it doesn't matter how long of a delay there is, they will have the same timestamp. I'm not sure why this is, and I'm not sure if this is documented behavior that you can rely on.
Control.MouseDoubleClick is not a bubble event but a direct event.
Since checking this question with Snoop, which is a tool for browsing visual trees and routed events, I see that Control.MouseDoubleClick events of 'ListView' and 'ListBoxItem' are fired at one time. You could check with this Snoop tool.
First, to find an answer, it is needed to check that both event arguments of the MouseDoublClick are same objects. You would expect they are same objects. If it is true, it is very strange as your question, but they are not same instances. We can check it with following codes.
RoutedEventArgs _eventArg;
private void Button_MouseDoubleClick(object s, RoutedEventArgs e)
{
if (!e.Handled) Debug.WriteLine("Button got unhandled doubleclick.");
//e.Handled = true;
_eventArg = e;
}
private void ListViewItem_MouseDoubleClick(object s, RoutedEventArgs e)
{
if (!e.Handled) Debug.WriteLine("ListViewItem got unhandled doubleclick.");
e.Handled = true;
if (_eventArg != null)
{
var result = _eventArg.Equals(e);
Debug.WriteLine(result);
}
}
It means that the event argument of the MouseDoublClick is created newly at somewhere, but I don't understand deeply why it is.
To be clearer, let's check for the event argument of the BottonBase.Click. It will be return the true about checking same instances.
<ListView>
<ListViewItem ButtonBase.Click="ListViewItem_MouseDoubleClick">
<Button Click="Button_MouseDoubleClick" Content="click"/>
</ListViewItem>
</ListView>
If you only focus on the execution as you mentioned there'll be lots of solutions. As above, I think that using the flag(_eventArg) is also good choice.
I've just had this same problem. There is a simple but non-obvious solution.
Here is how double click is raised by Control ....
private static void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
Control control = (Control)sender;
MouseButtonEventArgs mouseButtonEventArgs = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice);
if (e.RoutedEvent == UIElement.PreviewMouseLeftButtonDownEvent || e.RoutedEvent == UIElement.PreviewMouseRightButtonDownEvent)
{
mouseButtonEventArgs.RoutedEvent = Control.PreviewMouseDoubleClickEvent;
mouseButtonEventArgs.Source = e.OriginalSource;
mouseButtonEventArgs.OverrideSource(e.Source);
control.OnPreviewMouseDoubleClick(mouseButtonEventArgs);
}
else
{
mouseButtonEventArgs.RoutedEvent = Control.MouseDoubleClickEvent;
mouseButtonEventArgs.Source = e.OriginalSource;
mouseButtonEventArgs.OverrideSource(e.Source);
control.OnMouseDoubleClick(mouseButtonEventArgs);
}
if (mouseButtonEventArgs.Handled)
{
e.Handled = true;
}
}
}
So if you handle PreviewMouseDoubleClick setting e.Handled = true on the child control MouseDoubleClick won't fire on the parent control.
You cannot easily change the way double clicking events get fired because they are dependent on user settings and that delay is customized in control panel.
You should checkout RepeatButton that allows you to press's button and while it is pressed it generates multiple click events in regular sequence.
In case if you want to customize event bubbling then you should search for Preview events that allows you to block propogation of events. What are WPF Preview Events?

Clear MenuItem, with clear command

hi
i'm a "very" beginner in wpf
i'm trying to make a menu item "Clear", it should clear the text in the focused text box,
actually i could not find a built in command that does the job like (copy,paste,cut..etc)
is there one built in or do i have to make a custom routed command, and if so
i've tried but failed, and need ideas
i've made the ClearCommandExecuted logic, but the problem is with "CanExecute"
i tried to access the Keyboard.FocusedElement there, but failed because the focused element is the menu item it self when it's clicked !!!!
please help
thanks
You need to use one of the arguments passed into your CanExecuteQuery:
private void ClearCommandBindingCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// e.Source is the element that is active,
if (e.Source is TextBox) // and whatever other logic you need.
{
e.CanExecute = true;
e.Handled = true;
}
}
private void ClearCommandBindingExecuted(object sender, ExecutedRoutedEventArgs e)
{
var textBox = e.Source as TextBox;
if (textBox != null)
{
textBox.Clear();
e.Handled = true;
}
}
I hope this is enough to get you headed in the right direction...
Try to use the FocusManager class. When your TextBox has lost Keyboard Focus, it still has Logical Focus, if it is inside the Focus Scope. Classes in WPF which are focus scopes by default are Window, MenuItem, ToolBar, and ContextMenu.
So using this will give you the result -
FocusManager.GetFocusedElement(winodw1); //Name of the window
For more details, read this - http://msdn.microsoft.com/en-us/library/aa969768.aspx

TreeNode Selection Problems in C#

Whenever I click outside the tree nodes text, on the control part, it tigers a node click event- but doesn't highlight the node. I am unsure why this is happening.
I want the node to be selected on a click- when you click the nodes text- not the whitespace- I only assume that the nodes width reaches across the whole Treenode? I have the Treeview on dock.fill mode if that has something to do with it- I tried everything but can't get it to behave correctly.
Maybe someone will know what's going on.
Update:
if (e.Location.IsEmpty)
{
Seems to work better- but still selects the node in the blank place where there is no text- Obviously the node width extends across the whole treeview it seems?
Is there a better way to accomplish what I want? Or is that the best way?
UPDATE: Previous idea isn't working- sigh- I thought it did it but it didn't.
New Problem : I think part of the problem is related to the focus now when I switch from treeview.
UPDATE-
The only code I came up with about disabling right mouse click to select node on beforeSelect event is
if (MouseButtons == System.Windows.Forms.MouseButtons.Right)
{
e.Cancel = true;
}
But it didn't work- any help is appreciated- following suggestions of only answer, for more details.
You should use the treeView.HitTest method to determine which part of the node has been clicked.
private bool IsClickOnText(TreeView treeView, TreeNode node, Point location)
{
var hitTest = treeView1.HitTest(location);
return hitTest.Node == node
&& hitTest.Location == TreeViewHitTestLocations.Label;
}
private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
if(IsClickOnText(treeView1, e.Node, e.Location))
{
MessageBox.Show("click");
}
}
private void treeView1_BeforeSelect(object sender, TreeViewCancelEventArgs e)
{
if (e.Action == TreeViewAction.ByMouse)
{
var position = treeView1.PointToClient(Cursor.Position);
e.Cancel = !IsClickOnText(treeView1, e.Node, position);
}
}
Use the .AfterSelect and/or .BeforeSelect events to handle the selection processing instead of the .Click event. Then it will select the node only when you click on the text, and it won't fire .AfterSelect or .BeforeSelect when you click on the white space.

Categories

Resources