I want to be able to drag over a bunch of controls and select those of a certain type (TextBoxes).
Once the dragging action has been completed, I want to display an inputbox (yes, I'll have to reference/use the VB .dll) prompting the user for the value that will be entered in each selected TextBox.
Can this be done? (of course, but how?)
Or is there another way to accomplish the same thing (allow the user to quickly select multiple controls and then perform an action on all of them at once)?
Updated:
I've got this sort of working - the "caveat" or "gotcha" being that I have to pop up a MessageBox.Show() to the user for it to work. Essentially, I:
Set a boolean to true on the container's (FlowLayoutPanel, in my case) MouseDown event, if the right mouse button was selected.
Set that same boolean to false on the container's MouseUp event, if the right mouse button was selected.
I then have a shared MouseHover event handler for all of the TextBoxes on that form that, if the boolean is true, changes the BackColor (to Gainsboro, in my case, from Window).
In the container's MouseUp event, I also use an InputBox (referencing/importing/using the VB .dll) requesting the user enter the value that will be common for the "highlighted" TextBoxes. I then loop through them, looking for those with that BackColor, and assing the user-supplied value to their Text properties.
Voila!
Unfortunately, the TextBoxes' Modified property does not seem to be altered when you assign it values this way, so I had to work around that (explicitly setting the "Save" button to enabled), and I had to add more code to duplicate my KeyPressed code which limits the values entered by the user.
So, it is, of course, possible, albeit a little kludgy. I haven't decided if the MessageBox.Show() is a "bug" or a feature, though...
A related post is: Why does MouseHover event only get called if a messagebox.show() or breakpoint occurs before it?
This is actually very simple. I assume by drag you mean that you want to 'select' controls, like you would 'select' some pixels in a paint program for example. Here is an example I wrote you that does just this and only selects TextBox controls. It ignores other controls.
namespace WindowsFormsApplication5
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
/// <summary>
/// Main application form
/// </summary>
public partial class Form1 : Form
{
/// <summary>
/// Initializes a new instance of the WindowsFormsApplication5.Form1 class
/// </summary>
public Form1() {
InitializeComponent();
DoubleBuffered = true;
}
private Point selectionStart;
private Point selectionEnd;
private Rectangle selection;
private bool mouseDown;
private void GetSelectedTextBoxes() {
List<TextBox> selected = new List<TextBox>();
foreach (Control c in Controls) {
if (c is TextBox) {
if (selection.IntersectsWith(c.Bounds)) {
selected.Add((TextBox)c);
}
}
}
// Replace with your input box
MessageBox.Show("You selected " + selected.Count + " textbox controls.");
}
protected override void OnMouseDown(MouseEventArgs e) {
selectionStart = PointToClient(MousePosition);
mouseDown = true;
}
protected override void OnMouseUp(MouseEventArgs e) {
mouseDown = false;
SetSelectionRect();
Invalidate();
GetSelectedTextBoxes();
}
protected override void OnMouseMove(MouseEventArgs e) {
if (!mouseDown) {
return;
}
selectionEnd = PointToClient(MousePosition);
SetSelectionRect();
Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (mouseDown) {
using (Pen pen = new Pen(Color.Black, 1F)) {
pen.DashStyle = DashStyle.Dash;
e.Graphics.DrawRectangle(pen, selection);
}
}
}
private void SetSelectionRect() {
int x, y;
int width, height;
x = selectionStart.X > selectionEnd.X ? selectionEnd.X : selectionStart.X;
y = selectionStart.Y > selectionEnd.Y ? selectionEnd.Y : selectionStart.Y;
width = selectionStart.X > selectionEnd.X ? selectionStart.X - selectionEnd.X : selectionEnd.X - selectionStart.X;
height = selectionStart.Y > selectionEnd.Y ? selectionStart.Y - selectionEnd.Y : selectionEnd.Y - selectionStart.Y;
selection = new Rectangle(x, y, width, height);
}
}
}
Now there are limitations with this currently. The obvious is that this will not select a TextBox within a nested container control (eg. a panel on your form containing a TextBox). If that were the case the selection would be drawn underneath the panel, and the TextBox would not be selected because the code I wrote does not check nested containers.
You can easily update the code to do all of this however, but this should give you a solid start.
Related
I created a custom form to show it as a ToolTip but to show data on it, the show will be display when the user hovers on a Button and disappear on mouse leave, everything works perfectly but how can I prevent the Form from display outside the screen area??
Here is the code I used to show the Form:
Info_Form tooltip = new Info_Form();
private void Button131_MouseHover(object sender, EventArgs e)
{
Button b = (Button)sender;
tooltip = new Info_Form();
tooltip.Height = tooltip.Height + 30;
tooltip.Location = new Point(
b.Right,
b.Top);
tooltip.Show();
}
private void Button131_MouseLeave(object sender, EventArgs e)
{
tooltip.Close();
}
I suggest to:
declare your ToolTip Form in Program.cs
add static Methods to show and hide it
Of course you can use a dedicated class that exposes static methods to handle your ToolTip Form
You can then reach your ToolTip Form from anywhere in your application, just call ShowToolTip() and HideToolTip() when the Mouse pointer enters or leaves a specific Control: the bounds of this Control, translated to Screen coordinates, are used as reference to position the ToolTip.
Here, I'm using a simplified method to determine whether the ToolTip should be shown to the right or left and to the top or bottom of the reference Control:
if the reference Control is positioned to the left half of the screen, then the left potion of the Form is: ToolTip.Left = [ref Control].Left
otherwise, ToolTip.Left = [ref Control].Right - ToolTip-Width
the same logic applies to the top position of the ToolTip
Adjust this simple calculation to position your ToolTip Form using a different logic, if needed.
▶ There's no need to dispose of the ToolTip Form: it's automatically disposed of when the Form instance passed to Application.Run() is closed.
Note: If the Application is not DpiAware and the Screen where the Application's Windows are shown is scaled, any measure relative to either the Screen or the Windows/Forms may be virtualized, so you receive wrong results and any calculation will be off.
See the notes here:
Using SetWindowPos with multiple monitors
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
public static frmToolTip tooltip = null;
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
tooltip = new frmToolTip();
Application.Run(new SomeForm());
}
public async static Task ShowToolTip(Control control, string title) {
await Task.Delay(400);
if (tooltip.Visible) return;
Rectangle refBounds = control.RectangleToScreen(control.ClientRectangle);
if (!refBounds.Contains(Cursor.Position)) {
HideToolTip();
}
else {
var screen = Screen.GetBounds(control);
var leftPos = refBounds.Left <= screen.Width / 2 ? refBounds.Left : refBounds.Right - tooltip.Width;
var topPos = refBounds.Top <= screen.Height / 2 ? refBounds.Bottom + 2: refBounds.Top - tooltip.Height - 2;
tooltip.Location = new Point(leftPos, topPos);
tooltip.Text = title;
tooltip.Show(control.FindForm());
}
}
public static void HideToolTip() => tooltip.Hide();
In any Form, use a Control's MouseEnter and MouseLeave events to show/hide your ToolTip.
Note: I'm using Task.Delay() to delay the presentation of the ToolTip, so it doesn't show up if you briefly move the Mouse Pointer over the Control that shows it.
It also verifies whether the Form ToolTip is already shown (you cannot show a Form twice) or the Mouse Pointer has moved outside the bounds of the reference Control in the meanwhile (the ToolTip is not shown in this case).
Change this behavior as required.
In ShowToolTip(), I'm passing a string, meant to be used as the Title of the ToolTip. It's just an example, to show that you may pass any other parameter to this method (a class object, maybe) to setup the ToolTip Form in a custom way, different based on the caller requirements.
using System.Threading.Tasks;
public partial class SomeForm : Form
{
// [...]
private async void SomeControl_MouseEnter(object sender, EventArgs e)
{
await Program.ShowToolTip(sender as Control, "Some Title Text");
}
private void SomeControl_MouseLeave(object sender, EventArgs e)
{
Program.HideToolTip();
}
}
I'm running into some situations where it has been nice to make a control (Control A) with a bunch of things in it like buttons, listboxes, datagridviews etc so I can drag it into other controls (Control B) whenever I like, so I don't have to write the logic for the stuff within Control A over and over again.
The only annoying thing with this approach has been that I can't seem to find a way in the designer to move/resize the buttons, listboxes, datagridviews etc of Control A around from the designer of Control B. All it seems to let me do is resize the entirety of Control A.
Does anyone know how to make these custom controls containing multiple controls in such a way that they support design time resizing/moving?
Thanks
Isaac
There is no built in way to do this. You would essentially have to implement event handlers in order to handle the resizing of individual components within your control. One alternative for you is to expose the Size and Location properties of each individual component to the control's clients. For example, within the Control class you could do something like this:
public Size Component1Size
{
get { return component1.Size; }
set { component1.Size = value; }
}
public Point Component1Location
{
get { return component1.Location; }
set { component1.Location = value; }
}
and do this for each component of your control. I think this would be your best option, even though the user won't be able to physically click/drag the components to move and resize them.
Yes you can my friend ,you need to create a SizeAble and DragAndDrop pannel where you can Insert Controls and by Moving That Pannel you can reach that .And for the Resizing Issue you can play with Anchor of Control's that you already added .
using System;
using System.Drawing;
using System.Windows.Forms;
public class SizeablePanel : Panel {
private const int cGripSize = 20;
private bool mDragging;
private Point mDragPos;
public SizeablePanel() {
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.BackColor = Color.White;
}
protected override void OnPaint(PaintEventArgs e) {
ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor,
new Rectangle(this.ClientSize.Width - cGripSize, this.ClientSize.Height - cGripSize, cGripSize, cGripSize));
base.OnPaint(e);
}
private bool IsOnGrip(Point pos) {
return pos.X >= this.ClientSize.Width - cGripSize &&
pos.Y >= this.ClientSize.Height - cGripSize;
}
protected override void OnMouseDown(MouseEventArgs e) {
mDragging = IsOnGrip(e.Location);
mDragPos = e.Location;
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e) {
mDragging = false;
base.OnMouseUp(e);
}
protected override void OnMouseMove(MouseEventArgs e) {
if (mDragging) {
this.Size = new Size(this.Width + e.X - mDragPos.X,
this.Height + e.Y - mDragPos.Y);
mDragPos = e.Location;
}
else if (IsOnGrip(e.Location)) this.Cursor = Cursors.SizeNWSE;
else this.Cursor = Cursors.Default;
base.OnMouseMove(e);
}
}
I am using this code to draw some circles but it keeps redrawing and redrawing and stops with error that textBox1.text has bad number format even when I try 5 or 6. What is wrong with this?
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace terc
{
/// <summary>
/// Description of MainForm.
/// </summary>
public partial class MainForm : Form
{
public MainForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
//
// TODO: Add constructor code after the InitializeComponent() call.
//
}
void Button1Click(object sender, EventArgs e)
{
this.Paint += new PaintEventHandler(KresliTerc);
}
protected void KresliTerc(object sender,PaintEventArgs e)
{
Graphics grfx = e.Graphics;
int pocet = int.Parse(textBox1.Text);
label1.Text = pocet.ToString();
for(int i=1; i <= pocet; i++)
{
grfx.FillEllipse(Brushes.Black,ClientSize.Width/2,ClientSize.Height/2,50*i,50*i);
Invalidate();
}
}
}
}
Why do you attach the event handler each time that the button is clicked? This means that you will have as many event handlers as button clicks, which is I doubt is what you want.
However, your repaint problem is probably the fact that you call Invalidate inside the Paint event handler. This will force a new repaint of the form. So for each time that you paint, you will trigger a new paint, which will trigger a new paint and so on.
You should not invalidate in a Paint call.
Handle the painting based on some integer or boolean values.
Set the integer or boolean values in click events and just call Invalidate in the button click handler.
this.Paint += new PaintEventHandler(KresliTerc);
where are you calling this , since it is a paint event it will call again and again when painting occurs
Boolean isButtonClicked;
protected override void OnPaint(PaintEventArgs e)
{
if (this.isButtonClicked)
{
this.isButtonClicked = false;
// some paint logic goes down here...
e.Graphics.FillEllipse(Brushes.YellowGreen, 12, 12, 54, 54);
}
}
private void HandleOnButtonClick(Object sender, EventArgs e)
{
this.isButtonClicked = true;
this.Invalidate();
}
i am developing keyboard control, very simple embedded on a form. using sendkey class to perform char entry. to make this functional is required to know previous selected control.
Something like the following should do the trick:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace DragDropTest
{
public partial class LostFocusTestForm : Form
{
private Control _lastControl;
public LostFocusTestForm()
{
InitializeComponent();
TrapLostFocusOnChildControls(this.Controls);
}
private void finalTextBox_Enter(object sender, EventArgs e)
{
MessageBox.Show("From " + _lastControl.Name + " to " + this.ActiveControl.Name);
}
private void AllLostFocus(object sender, EventArgs e)
{
_lastControl = (Control)sender;
}
private void TrapLostFocusOnChildControls(Control.ControlCollection controls)
{
foreach (Control control in controls)
{
control.LostFocus += new EventHandler(AllLostFocus);
Control.ControlCollection childControls = control.Controls;
if (childControls != null)
TrapLostFocusOnChildControls(childControls);
}
}
}
}
Expanding on David's answer. This is how you can use the Enter event and a variable to store the last control:
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
Control lastControlEntered = null;
public Form1()
{
InitializeComponent();
foreach (Control c in Controls)
if (!(c is Button)) c.Enter += new EventHandler(c_Enter);
}
void c_Enter(object sender, EventArgs e)
{
if (sender is Control)
lastControlEntered = (Control)sender;
}
private void button1_Click(object sender, EventArgs e)
{
label1.Text = lastControlEntered == null ? "No last control" : lastControlEntered.Name;
}
}
}
To run this code, add a few textboxes and other control to a Form in Visual Studio, and add a button and a label and attach the button's click handler to button1_Click. When you press the button, the last control you were in before pressing the button is displayed in the label. Edit this code to suit your needs.
You need to store it in a variable. For Control objects, there's "Enter" event
Another strategy is to use an Extension Method to extend Control.ControlCollection, and then using some indirection (a delegate) recursively parse the Controls collection of the Form adding a special "Enter" handler that updates a static variable. By keeping track of the previous Active Control and the current Active Control, you then have what you need ... if I understand your question fully. Here's an example that requires FrameWork 3.5 or 4.0.
// in a Public static class :
using System;
using System.Collections.Generic;
using System.Windows.Forms;
private static EventHandler _event;
// extension method on Control.ControlCollection
public static void SetEnterEvent(this Control.ControlCollection theCollection, EventHandler theEvent)
{
_event = theEvent;
recurseSetEnter(theCollection);
}
// recurse all the controls and add the Enter Event :
public static void recurseSetEnter(Control.ControlCollection aCollection)
{
foreach (Control theControl in aCollection)
{
// "weed out" things like internal controls of the NumericUpDown Control
// String.IsNullOrWhiteSpace is FrameWork 4.0
// use Trim() followed by String.IsNullOrEmpty for FrameWork 3.5
if (!String.IsNullOrWhiteSpace(theControl.Name))
{
Console.WriteLine("setting enter handler for : " + theControl.Name + " : " + theControl.GetType().ToString());
theControl.Enter += _event;
}
if (theControl.Controls.Count > 0) recurseSetEnter((Control.ControlCollection)theControl.Controls);
}
}
So how do we use this : in a Form :
First let's define an actual Event handler that is going to actually execute when the Enter event is encountered on any control :
We'll keep the current active control, and the previous active control, in public static variables :
public static Control theActiveControl = null;
public static Control thePreviousControl = null;
And here's the code that does the updating :
private void control_enter(object sender, EventArgs e)
{
thePreviousControl = theActiveControl;
theActiveControl = sender as Control;
Console.WriteLine("Active Control is now : " + theActiveControl.Name);
}
Now in the Form_Load event or elsewhere we just need to wire-up the events :
// in a Form**
// define a delegate for the enter Event
private event EventHandler enter = delegate { };
// in the form load even or somewhere : assign an actual event handler to the delegate
enter += new EventHandler(control_enter);
Finally we invoke the extension method on the Controls Collection of the Form :
this.Controls.SetEnterEvent(enter);
Discussion : a WinForm maintains an 'ActiveControl collection : this will contain a pointer to the most recently activated control no matter how deeply nested it is in one or more containers : ... some containers (like Panels) do not register as active controls in this collection even though they have Leave/Enter events ... controls are going to become the ActiveControl when they are used/selected/entered-into/focused-on, etc. Unfortunately there's no "ActiveControlChanged" event.
[edit] in practice I am developing this using"filters" so I can selectively skip over certain object types, or, for example, look for some "key" (in the control name or its tag) to determine whether or not to add the handler ... yes ... it's an experiment. [edit]
[edit] note that some controls like PictureBox expose no 'Enter event, but this code does not cause an error : my long-range goal is to find a way to test, without reflection, whether a particular control does expose a given 'event before I install one : since I consider it bad practice to just let things like PictureBox "wiggle through." So far I have not found the right "test" for "container-ness" ("is ControlContainer" turned out to be the wrong track). You may note also that Panels, for example, expose an 'Enter event, but it's only fired when some Control inside the Panel is activated. [edit]
Hope this is helpful. I am sure this could probably be written more elegantly using Lambdas, but as yet I am a "larva" feeding on the leaves of Skeet in that regard :)
You can do this by
string activeControl= this.ActiveControl.Name
You have to track this yourself. Write a trivial UIElement.LostFocus handler which puts the sender into a "last control with focus" variable, and you're done.
NOTE: WPF. Not sure if you're doing that or WinForms. I've been doing so much WPF lately I have it on the brain.
I had written an event handler for MouseMove for my form
but When I add a panel to form, this handler does NOT run while mouse moves on panel.
I added event handler to panel and this works but I had several panels on the form,
is there an easier solution?
Unfortunately, WinForms doesn't support event bubbling. But you can write some code to ease the task of hooking up events.
public void AssignMouseMoveEvent(Form form)
{
foreach(Control control in form.Controls)
{
if(! (control is Panel))
continue;
control.MouseMove += PanelMouseMove;
}
}
You should call the above code passing it your current form and it will assign PanelMouseMove as event handler for MouseMove event of all the panels.
I think you should be able to "propagate" the handlers, so you don't have to re-write the code in each one. Just remember that the MouseMove event has control-relative coordinates, so if you pass the event from your panel to your form, you'll have to translate the X & Y values in the event to the form coordinates (something like subtracting panel.location.X from event.X, etc).
This code worked for me (assumes you have a form with a panel and a label. The label is named "MouseCoords"
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void ShowCoords(int x, int y)
{
this.MouseCoords.Text = string.Format("({0}, {1})", x, y);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
this.ShowCoords(e.X, e.Y);
}
protected override void OnControlAdded(ControlEventArgs e)
{
// hook the mouse move of any control that is added to the form
base.OnControlAdded(e);
e.Control.MouseMove += new MouseEventHandler(Control_MouseMove);
}
private void Control_MouseMove(object sender, MouseEventArgs e)
{
// convert the mouse coords from control codes to screen coords
// and then to form coords
System.Windows.Forms.Control ctrl = (System.Windows.Forms.Control)sender;
Point pt = this.PointToClient(ctrl.PointToScreen(e.Location));
this.ShowCoords(pt.X, pt.Y);
}
private void Form1_Load(object sender, EventArgs e)
{
this.MouseMove += this.Form1_MouseMove;
}
}
}
You could Implement IMessageFilter to pre-process messages that are going to your controls.
http://blogs.msdn.com/csharpfaq/archive/2004/10/20/245412.aspx
However, I don't think this is a very clean way to do things from a design perspective.
No there is no simpler way, and you should assign event handler for each control where you need to receive MouseMove events.
If you set the Capture property of the form to true, it will receive all mouse input, regardless of which control that is under the mouse. It will lose the mouse capture at certain operations (I am not sure exactly when, though). Also, according to the documentation for the property, shortcut keys should not work while the mouse is captured. So, depending on what you want to achieve, this might not be the preferred way to go.
Assuming that mouse starts moving over the form rather than over the panel - which is a big assumption - you'll get a MouseLeave event when it enters a sub control. You could check the cursor location and call the mouse move code if it's still within the bounds of the form.
This doesn't work if the mouse move event starts on a control.
I found another solution :) "Raise events in controls which hide events"
I catch the event in panel and rise the Mouse move event of the form by calling onMouseMove