I have a bunch of paths programatically nested inside of a canvas. I'm basically trying to figure out how click bubbling works. How do I setup the canvas event handler to check if the point of the click was also on a path nested inside of the canvas. This is my basic even code that works if the paths are not nested.
How do I add bubbling click detection?
void Path_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var item = ((FrameworkElement)e.OriginalSource).DataContext as Path;
if (item != null)
{
MessageBox.Show(item.Name);
}
}
you add a handler on the event UIElement.MouseLeftButtonUpEvent (as Path inherits it from there) on the Canvas
theNestingCanvas.AddHandler(UIElement.MouseLeftButtonUpEvent , new RoutedEventHandler(target));
private void handler(object asd, RoutedEventArgs e)
{
Path p = e.OriginalSource as Path;
if (p != null)
{
//do whatever
}
e.Handled = true;
}
like that you catch all bubbled UIElement.MouseLeftButtonUp events of elements within the canvas which are not handled somewhere else yet...
of course you can also add the handler on the Event Path.MouseLeftButtonUpEvent but after you will ask yourself why you catch also the MouseUp events of other nested elements...
Related
Is there a way to allow Drag and Drop anywhere in a form full of controls?
The idea is to allow user to drag a file anywhere in a form in order to "load" it. I will not need any other DragDrop behavior but this.
By setting AllowDrop=True to the form only, I get DragEnter events but not DragDrop ones.
An idea would be to make a topmost panel visible on DragEnter and handle DragDrop events there, but I wonder if I miss something obvious here since I have little experience in the field.
Another Idea would be to iterate through all controls and subscribe to Drag-related events. I really don't like this approach, though.
Sure, iterating the controls will work, it doesn't take much code:
public Form1() {
InitializeComponent();
WireDragDrop(this.Controls);
}
private void WireDragDrop(Control.ControlCollection ctls) {
foreach (Control ctl in ctls) {
ctl.AllowDrop = true;
ctl.DragEnter += ctl_DragEnter;
ctl.DragDrop += ctl_DragDrop;
WireDragDrop(ctl.Controls);
}
}
void ctl_DragDrop(object sender, DragEventArgs e) {
// etc..
}
void ctl_DragEnter(object sender, DragEventArgs e) {
// etc..
}
If you still don't like the approach then use a recognizable single drop target that the user will always hit. Could be as simple as a label that says "Drop here".
I'm not sure what kinds of control you have on your form. But I've tested with a Button, a GroupBox, a PictureBox and a TextBox. All these controls have AllowDrop = false by default. And I can drag-n-drop something from outside onto the form OK. The DragDrop is fired OK. Everything is OK. What is actually your problem? I guess your controls have AllowDrop = true.
In the case the DragDrop event is not fired (which I think only happens if the target is one of your Control with AllowDrop = true). I think the following may work. But if the target is one of your Control with AllowDrop = true, the effect icon is gone away.
public Form1(){
InitializeComponents();
t.Interval = 1;
t.Tick += Tick;
}
IDataObject data;
Timer t = new Timer();
int i = 0;
private void Tick(object sender, EventArgs e)
{
Text = (i++).ToString();
if (ClientRectangle.Contains(PointToClient(new Point(MousePosition.X, MousePosition.Y))) && MouseButtons == MouseButtons.None)
{
t.Stop();
if (data != null)
{
//Process data here
//-----------------
data = null;
}
}
else if (MouseButtons == MouseButtons.None)
{
data = null;
t.Stop();
}
}
private void Form1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.AllowedEffect;
if (data == null)
{
data = e.Data;
t.Start();
}
}
And I think you may have to use the loop through all the Controls to add appropriate event handlers. There is no other better way.
In the Drop event.
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach (string file in files) Console.WriteLine(file);
In the DragEnter event.
if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effects = DragDropEffects.Copy;
Consider this is a ListView that shows files and folders, I have already wrote code for copy/move/rename/show properties ...etc and I just need one more last thing. how to drag and drop in the same ListView like in Windows Explorer, I have move and copy functions, and I just need to get the items which user drops in some folder or in other way I need to get these two parameters to call copy function
void copy(ListViewItem [] droppedItems, string destination path)
{
// Copy target to destination
}
Start by setting the list view's AllowDrop property to true. Implementing the ItemDrag event to detect the start of a drag. I'll use a private variable to ensure that D+D only works inside of the control:
bool privateDrag;
private void listView1_ItemDrag(object sender, ItemDragEventArgs e) {
privateDrag = true;
DoDragDrop(e.Item, DragDropEffects.Copy);
privateDrag = false;
}
Next you'll need the DragEnter event, it will fire immediately:
private void listView1_DragEnter(object sender, DragEventArgs e) {
if (privateDrag) e.Effect = e.AllowedEffect;
}
Next you'll want to be selective about what item the user can drop on. That requires the DragOver event and checking which item is being hovered. You'll need to distinguish items that represent a folder from regular 'file' items. One way you can do so is by using the ListViewItem.Tag property. You could for example set it to the path of the folder. Making this code work:
private void listView1_DragOver(object sender, DragEventArgs e) {
var pos = listView1.PointToClient(new Point(e.X, e.Y));
var hit = listView1.HitTest(pos);
if (hit.Item != null && hit.Item.Tag != null) {
var dragItem = (ListViewItem)e.Data.GetData(typeof(ListViewItem));
copy(dragItem, (string)hit.Item.Tag);
}
}
If you want to support dragging multiple items then make your drag object the ListView.SelectedIndices property.
I'm creating listviews in a flowpanel at run time which later will accept drag and dropped files. the reason being is i want these to act as folders so a user double clicks and gets a window displaying the contents.
i'm having difficulty setting up the events for my listviews as they are added.
how do i create some events (like MouseDoubleClick and DragDrop) dynamically for each added listview? can i create a single function for both of these events and have listview1, listview2, listviewX use it?
i have a button that is adding the listviews, which works fine. please advise, i apologize if this is too conceptual and not exact enough.
private void addNewWOButton_Click(object sender, EventArgs e)
{
ListView newListView = new ListView();
newListView.AllowDrop = true;
flowPanel.Controls.Add(newListView);
}
You would have to have the routine already created in your code:
private void listView_DragDrop(object sender, DragEventArgs e) {
// do stuff
}
private void listView_DragEnter(object sender, DragEventArgs e) {
// do stuff
}
and then in your routine, your wire it up:
private void addNewWOButton_Click(object sender, EventArgs e)
{
ListView newListView = new ListView();
newListView.AllowDrop = true;
newListView.DragDrop += listView_DragDrop;
newListView.DragEnter += listView_DragEnter;
flowPanel.Controls.Add(newListView);
}
You would have to check who the "sender" is if you need to know which ListView control is firing the event.
You can also just use a lambda function for simple things:
newListView.DragEnter += (s, de) => de.Effect = DragDropEffects.Copy;
Just make sure to unwire the events with -= if you also remove the ListViews dynamically.
To answer the other half of your question, you can use a single handler for any event, from any source, that has the handler's signature. In the body of the handler, you just have to check the sender argument to determine which control raised the event.
You need a way to tell one control from a different one of the same class, however. One way to do this is to make sure to set the Name property on each control when you create it; e.g., newListView.Name = "FilesListView".
Then, before you do anything else in your event handler, check the sender.
private void listView_DragDrop(object sender, DragEventArgs e) {
ListView sendingListView = sender as ListView;
if(sendingListView == null) {
// Sender wasn't a ListView. (But bear in mind it could be any class of
// control that you've wired to this handler, so check those classes if
// need be.)
return;
}
switch(sendingListView.Name) {
case "FilesListView":
// do stuff for a dropped file
break;
case "TextListView":
// do stuff for dropped text
break;
.....
}
}
I'm new to WPF. In my WPF app, I have Windows which contain a user defined child control and that user defined child control again contains another user defined child control. Now from the inner most child control, on a button click, I want to fire events on all three controls (i.e. First Grand Child Control, Second Child Control, Third Main Control, and Window).
I know this can be achieved through delegates and Event Bubbling. Can you please tell me how?
Most important piece pf code for that:
Add the event handlers on the static UIElement.MouseLeftButtonUpEvent:
middleInnerControl.AddHandler(UIElement.MouseLeftButtonUpEvent , new RoutedEventHandler(handleInner)); //adds the handler for a click event on the most out
mostOuterControl.AddHandler(UIElement.MouseLeftButtonUpEvent , new RoutedEventHandler(handleMostOuter)); //adds the handler for a click event on the most out
The EventHandlers:
private void handleInner(object asd, RoutedEventArgs e)
{
InnerControl c = e.OriginalSource as InnerControl;
if (c != null)
{
//do whatever
}
e.Handled = false; // do not set handle to true --> bubbles further
}
private void handleMostOuter(object asd, RoutedEventArgs e)
{
InnerControl c = e.OriginalSource as InnerControl;
if (c != null)
{
//do whatever
}
e.Handled = true; // set handled = true, it wont bubble further
}
Have a look at this http://msdn.microsoft.com/en-us/library/ms742806.aspx
This page explains all about routed events, including how to implement and consume them.
I am listening for the loaded event of a Page. That event fires first and then all the children fire their load event. I need an event that fires when ALL the children have loaded. Does that exist?
I hear you. I also am missing an out of the box solution in WPF for this.
Sometimes you want some code to be executed after all the child controls are loaded.
Put this in the constructor of the parent control
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => {code that should be executed after all children are loaded} ));
Helped me a few times till now.
Loaded is the event that fires after all children have been Initialized. There is no AfterLoad event as far as I know. If you can, move the children's logic to the Initialized event, and then Loaded will occur after they have all been initialized.
See MSDN - Object Lifetime Events.
You can also use the event: ContentRendered.
http://msdn.microsoft.com/en-us/library/ms748948.aspx#Window_Lifetime_Events
WPF cant provide that kind of an event since most of the time Data is determining whther to load a particular child to the VisualTree or not (for example UI elements inside a DataTemplate)
So if you can explain your scenario little more clearly we can find a solution specific to that.
One of the options (when content rendered):
this.LayoutUpdated += OnLayoutUpdated;
private void OnLayoutUpdated(object sender, EventArgs e)
{
if (!isInitialized && this.ActualWidth != 0 && this.ActualHeight != 0)
{
isInitialized = true;
// Logic here
}
};
Put inside your xaml component you want to wait for, a load event Loaded="MyControl_Loaded" like
<Grid Name="Main" Loaded="Grid_Loaded"...>
<TabControl Loaded="TabControl_Loaded"...>
<MyControl Loaded="MyControl_Loaded"...>
...
and in your code
bool isLoaded;
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
isLoaded = true;
}
Then, inside the Event triggers that have to do something but were triggering before having all components properly loaded, put if(!isLoaded) return; like
private void OnButtonChanged(object sender, RoutedEventArgs e)
{
if(!isLoaded) return;
... // code that must execute on trigger BUT after load
}
I ended up doing something along these lines.. your milage may vary.
void WaitForTheKids(Action OnLoaded)
{
// After your children have been added just wait for the Loaded
// event to fire for all of them, then call the OnLoaded delegate
foreach (ContentControl child in Canvas.Children)
{
child.Tag = OnLoaded; // Called after children have loaded
child.Loaded += new RoutedEventHandler(child_Loaded);
}
}
internal void child_Loaded(object sender, RoutedEventArgs e)
{
var cc = sender as ContentControl;
cc.Loaded -= new RoutedEventHandler(child_Loaded);
foreach (ContentControl ctl in Canvas.Children)
{
if (!ctl.IsLoaded)
{
return;
}
}
((Action)cc.Tag)();
}