Click here to Skip to main content
15,867,686 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I have a FlowLayoutPanel control in my form , an UserControl class to add a PictureBox.
I want to display some images from a folder, and when I click on an image it's border will be change , when I click outside of it , it's border is change again.


http://postimg.org/image/pt3qngulx/[^]


Here some code in my UserControl class

C#
public bool clicked;
public MyImageViewer()
{
    this.MouseClick += Control_MouseClick;
    this.ControlAdded += MyImageViewer_ControlAdded;

    InitializeComponent();
}

void MyImageViewer_ControlAdded(object sender, ControlEventArgs e)
{
    e.Control.MouseClick += Control_MouseClick;
}

void Control_MouseClick(object sender, MouseEventArgs e)
{
    this.BackColor = Color.LightBlue;
    this.clicked = true;
}

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    if (clicked)
    {
        ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, Color.Blue,                                     ButtonBorderStyle.Solid);
    }

}



But how can I detect the mouse is clicked outside of my usercontrol?
Posted
Updated 24-Apr-15 14:49pm
v2
Comments
Philippe Mori 24-Apr-15 22:42pm    
Not much details. Outside of my usercontrol is not specific enough. Do you want to consider clicks only in the view area (similar to Windows File explorer), any click on the parent form, in the application or on the system?

It seems that you display multiple images but it is not clear if you want a single selection, multiple selection or you want to hide selection when the view is not active.

The best solution depends on what you want to achieve. If there is a single selection in the view, it might even be easier to handled click at the container level instead.
Toan Pham Anh 25-Apr-15 8:43am    
I try to do similar as window explorer does. Begin with a single selection.
Philippe Mori 24-Apr-15 22:45pm    
By the way, if you add event Handler when adding a child control dynamically, you need to remove that Handler when the control is removed.
Toan Pham Anh 25-Apr-15 8:24am    
Thanks a lot

Well, you should consider detecting when the control become inactive (loose the focus).

You cannot directly detect a click outside the control since event are always directed toward the control that was clicked.

Well, I think that you can detect click inside your application if the application grab mouse capture and you indirectly detect a click outside the application as it become inactive when another application is activated (but you won't reliably know for sure that it was a click (and not Alt+Tab for example).

In practice, however detecting focus/activation change should be more than enough. Why would you keep the border if another Windows is activated using the keyboard and then the mouse is not used in that application? It would not be intuitive to your user that cliking on another application to activate it would remove the norder while using the keyboard would not.

Well, there could be tricks to detect clicks anywhere (transparent window, system hook...) but you really don't want to go that road if you don't have to as your application could then have a major impact on the stability of the system.

Update
To modify control style, you can use Control.SetStyle (https://msdn.microsoft.com/en-us/library/system.windows.forms.control.setstyle(v=vs.110).aspx[^]).

Selectable (https://msdn.microsoft.com/en-us/library/system.windows.forms.controlstyles(v=vs.110).aspx[^]) would probably be the one you want to change.

By the way, have you consider using a list view or some similar control. It might be a better solution for your problem.

Alternatively, if you want lot of control on the way the view is displayed, hosting a WPF control would probably give the best result.
 
Share this answer
 
v2
Comments
Sergey Alexandrovich Kryukov 24-Apr-15 21:32pm    
Sorry, this is a wrong solution. The trouble is: not all controls are focusable.
I described the really general solution; please see Solution 2.
—SA
Philippe Mori 24-Apr-15 21:51pm    
Well, in a case like that, the control should probably be made focusable so that the viewer would be reachable using Tab key. By overriding a control, you can usually make it work the way you want.
Sergey Alexandrovich Kryukov 24-Apr-15 22:07pm    
I would really suggest to use more general and actually simpler way.
—SA
Philippe Mori 24-Apr-15 22:34pm    
Well OP didn't give much details. I would think that in its case, you would only need to handle clicks on items and on the container as it should probably works similar to say Windows Explorer (any click outside the view does not affect the selection).

At first, I was more trying to give a general solution that would handle clicks anywhere. In fact, as OP mentionned outside the control, I assume anywhere on screen maybe to hide the selection when the view is not active.
Sergey Alexandrovich Kryukov 24-Apr-15 22:49pm    
Not much detail is just one more argument in favor of more general solution. What I describe literally answer the question in the title "How to detect when mouse is clicked outside a winform control?", no matter what.
—SA
I assume you are creasting a UserControl with a PictureBox inside it, and filling the Cells of a TableLayoutPanel with instances of the UserControl.

I assume you are doing this because you've found that putting only a PictureBox into TableLayoutPanel Cell gives an unsatisfactory visual result when you want to indicate it is the currently selected PictureBox ... because when the PictureBox is docked inside the Cell, and you set its BorderStyle: it looks like crap.

And, also: when you have the UserControl in a TableLayoutCell, you have no way at design-time ... in the Form where the UserControl is used ... to access the PictureBox in the UserControl to set a Click EventHandler for it.

If those assumptions are correct, here's one way you can get the "active border" visible in a consistent way effect:

I. In the UserControlL: create a Click EventHandler for the PictureBox, and in that Handler call a simple delegate of type Action:
C#
public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    public Action<UserControl1> Clicked;

    private void pictureBox1_Click(object sender, EventArgs e)
    {
        Clicked(this);
    }
}
Here we are creating an executable method (delegate) that is exposed publicly.

II. In the Form with the TableLayoutPanel we will inject the specific executable code we want into each instance of the UserControl in the TableLayoutPanel:
C#
private void Form1_Load(object sender, EventArgs e)
{
    foreach (UserControl1 uc1 in tableLayoutPanel1.Controls)
    {
        uc1.Clicked = PictureBox_Click;
    }
}
III. So now we can define the code we want to handle highlighting the active (clicked) UserControl:
C#
private UserControl1 lastPictureBoxClicked = null;
private UserControl1 currentPictureBoxClicked = null;

private void PictureBox_Click(UserControl1 uc1)
{
    if (lastPictureBoxClicked != null) lastPictureBoxClicked.BackColor = Color.GhostWhite;

    currentPictureBoxClicked = uc1;

    currentPictureBoxClicked.BackColor = Color.Red;

    lastPictureBoxClicked = currentPictureBoxClicked;
}
You could also just expose the PictureBox Control (by making a Public reference to it available) in UserControl code, which would mean you could define an EventHandler for it in the Form where you use the TableLayoutPanel. And, if you need to do other things at run-time in your App with the PictureBox, then that may be a good idea.

In the example here I chose not to expose the PictureBox Control, but only expose a way to have it raise an Event.
 
Share this answer
 
v3
Comments
Philippe Mori 25-Apr-15 8:34am    
Assuming this usage, I would suggest to have a "Selected" property on the user control and possibly for image and border color so that the control would be as much as possible self contained.
BillWoodruff 25-Apr-15 13:49pm    
Hi Philippe, I always appreciate your attempts to help the OP in a practical way !

If only being blind and feeling an elephant in a dark room would make us ... wise :)
Toan Pham Anh 26-Apr-15 5:10am    
I tried this way and it has done well. Thanks for help me! Continue look at solution 2
One little problem is that a form is normally covered by other controls, children of the form, so it won't get mouse events dispatched to it (in contrast to keyboard events, by the way). So, you can artificially "bubble" some other event to the form. This is pretty easy to do.

For simplicity, let's assume you write all the relevant code in just one class, some Form class. Create some event arguments class, derived from System.EventArgs. Main property it needs is the reference to the control which was the original event target (the control a mouse clicks on). Add a control instance to your form.

Subscribe to some mouse event on all the control of the form. This is done by recursively traversing all the control and adding the handler to each instance. It's better to subscribe to MouseDown not Click, because Click — surprise! — is not a mouse event; it is a higher-level "logical" event invoked by either mouse or keyboard. In this event handler, common for all those controls, invoke the event defined as described in the previous paragraph. Pass the first event argument ("sender") of the handled event to the "original event target" property of the invoked event. In other words, you get a sender from a mouse event you handle and redirect this data to the event instance you invoke on the form.

This way, any handler added to the invocation list of the forms event you define will get a notification that on some child control the mouse even was invoked, and tell you on what child control. Then compare the reference of this child control with the reference to the user control in your question. If these are different object, it mean that the user clicked outside of your user control. Simple, isn't it.

I mean, it's simple. Please try to write this simple code; it will be just few lines. But if something is unclear or seems to be difficult, please ask your questions; then I'll show you more detail. Please be specific. But I hope you can do it right away.

Let's consider one simple example:

[EDIT]

First, let's define some event arguments class:
C#
class ClickFormControlEventArgs : System.EventArgs {
    internal ClickFormControlEventArgs(Control originalTarget) {
        this.OriginalTarget = originalTarget;
    }
    internal Control OriginalTarget { get; private set; }
}

Now, the form, any form, main or not:
C#
public partial class MainForm : Form {

    void AddMouseDownEventHandler(Control control, MouseEventHandler handler) {
        control.MouseDown += handler;
        foreach (Control child in control.Controls)
            AddMouseDownEventHandler(child, handler);
    } //AddClickEventHandlerToChildren

    public MainForm() {
        InitializeComponent();
        Text = Application.ProductName;
        // to all invocation lists of the MouseDown event instance
        // of all the child controls, recursively:
        AddMouseDownEventHandler(this, (sender, eventArgs) => {
            if (FormControlClicked != null) {
                // we can be sure that sender is always control
                Control control = (Control)sender;
                FormControlClicked.Invoke(
                    sender,
                    new ClickFormControlEventArgs(control));
            } //if
        });
    } //MainForm

    internal event System.EventHandler<ClickFormControlEventArgs>
        FormControlClicked;

    // ...

} //class MainForm


Now, each event handler added to the invocation list of the form's even instance FormControlClicked will get notification of some. For general example, I'll show some code outside the form class, assuming that we have the instance of the form with this event. In case you do it inside the form class, it could be some method where the instance of the form is "this" (instead of "form" in the code below).

C#
Control control = //...; // some control we want to check up
Form form = //... "this", if this code withing the call.

// now, pay attention that we handled <code>MouseDown</code> on all the controls
// on the form, including the form itself; so we can check it up:

form.FormControlClicked += (sender, eventArgs) => {
   if (eventArgs.OriginalTarget == control) {
      // mouse was pressed down on this very control 
   } else if (eventArgs.OriginalTarget == form) {
      // mouse was pressed down on the form;
      // not on any of the form's children,
      // but on some form client area free from controls
   } else {
      // mouse was pressed on the form
      // but outside the control in question 
   }
}


Now, look at the objections by Philippe Mori who concerned about disruption in usual event handling of other events on all those controls, higher-level relative to this MouseDown. No, it won't be a problem, because you never set an event handler and never block other event handler or event propagation. Other event handler will do their usual work and different event handlers won't interfere.

—SA
 
Share this answer
 
v5
Comments
Philippe Mori 25-Apr-15 12:24pm    
If you hook all controls on a form (and not only inside the container as in Solution 3), that solution might bring its own problems if some buttons like Apply, Cancel or Help must not affect the selection.
Sergey Alexandrovich Kryukov 25-Apr-15 20:21pm    
I don't think so. You need to handle even transparently, so, none of the functionality of the controls should be compromised.
—SA
Sergey Alexandrovich Kryukov 26-Apr-15 19:45pm    
Please see the update to the question I wrote on the inquirer's request, after [EDIT]. I tried to explain why your the problem you pointed out won't be a problem.
—SA
Toan Pham Anh 26-Apr-15 17:10pm    
Do you mean that as solution 3 ?
Toan Pham Anh 26-Apr-15 18:45pm    
Dear Sergey Alexandrovich KryuKov! I'm going to accept your solution. Would you like write down your codes here. It's easy for you , aren't you ? Just want to see how your event argument class is. I have not created something like that :D
After a few solutions, I wounder if handling clicks only at the container level (FlowLayoutPanel) might not be easier as you won't have to manage adding and removing events handlers.

You would simply find on which child the user has clicked and update the selection accordingly.



Alternatively, why not use a list view or a data grid or a similar control? You would probably get more functionnality with less effort.


By the way, if you need to do custom painting anyway, it might be not much harder to use a single user control and paint all images and adorments directly.


Or if you know WPF enough, just host a WPF user control that would be designed to works the way you want.

In my previous solution, I was trying to have a solution that would detect any click even outside the application... This could be a good approach for things like closing a combo box when the application loose focus or hide selection when the application is not active but for displaying a view that act more or less like Windows File Explorer, this new solution would be more appropriate.
 
Share this answer
 
v2
Comments
Toan Pham Anh 26-Apr-15 19:12pm    
I want to display images as thumbnails and I think using a user control and put a picturebox on it is a easy way. I wounder a listview can only display some icons , it is not desirable.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900