I'm trying to make something like a spellchecker, that will list possible words under the current caret position. I thought I would do this by creating a tooltip, moving it according to the caret's location, and changing the text inside the tooltip.
I'm having problems.
I'm trying to show the tooltip with tip.Show(form, x, y);
However, this app is running from the systray. It has no GUI elements aside from that? What do I use as the form parameter? the notifyIcon1, Form1, etc. do not work.
I would start with an example that displayed a static tooltip that moved along with my mouse cursor or something. Can someone point me in the right direction?
Thanks
You may be able to do this but not using a tooltip class as that is quite limiting, there is a fantastic tooltip helper called VXPLib, using html formatting (which I suppose would give your listing of words an edge - say in different colours). The VXPLib is a COM object (written in C++) but accessible from the .NET language and there is a wrapper that can do it for you along with code samples. I have tried them and they actually work and make it look nice...See here for more information.
Hope this helps,
Best regards,
Tom.
I posted an answer in this thread that uses a transparent, maximized for to simulate drawing a tooltip anywhere on the screen, including the desktop. Maybe it will help: Creating a tooltip from a system-tray only app
Edit: Copied the code over from the linked post for ease of reading :-)
Here you go, use a transparent, maximized form that you BringToFront() before showing the ToolTip
Form1 Code:
using System;
using System.Windows.Forms;
namespace SO_ToolTip
{
public partial class Form1 : Form
{
Random _Random = new Random();
ToolTip _ToolTip = new ToolTip();
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
BringToFront();
_ToolTip.Show("Blah blah... Blah blah... Blah blah...", this, _Random.Next(0, Width), _Random.Next(0, Height), 10000);
}
}
}
Form1 Designer Code: So you can see the forms properties:
namespace SO_ToolTip
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 264);
this.ControlBox = false;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "Form1";
this.Opacity = 0;
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer timer1;
}
}
Update: With ControlBox = false; and Opacity = 0; the form is not only visually transparent but is immune to user input. That is even when the Form1 above if the top most window clicking in it's area falls through to the next window/desktop. Just as if the form wasn't there. The BringToFront() before showing the tooltip is required because otherwise the tooltip could be drawn under other windows, which is not what you want.
If there's no GUI in your application, then in what application are you to providing a spell checker?
If you are integrating your application with another existing application (even non-.NET applications), then you need to obtain the handle (HWND) of the other application and convert it to a System.Windows.Forms.IWin32Window. Once you do this, you can use that handle as the form in the ToolTip.Show method.
Here is the code you need:
using System.Diagnostics;
//...
public class MyWindow : System.Windows.Forms.IWin32Window
{
private IntPtr _hwnd;
public IntPtr Handle
{
get
{
return _hwnd;
}
}
public MyWindow(IntPtr handle)
{
_hwnd = handle;
}
//...
public static MyWindow GetWindowFromName(string processName)
{
Process[] procs = Process.GetProcessesByName(processName);
if (procs.Length != 0)
{
return new MyWindow(procs[0].MainWindowHandle);
}
else
{
throw new ApplicationException(String.Format("{0} is not running", processName));
}
}
}
//...
tip.Show("this worked...", MyWindow.GetWindowFromName("Notepad"), 0, 0, 2000);
I have worked on creating a tooltip that is "not linked to any particular control", because I wanted to replace one of my AutoHotkey scripts which uses the ToolTip command.
I have my code stored at: https://bitbucket.org/tahir-hassan/dotnettooltip
All you do is, instantiate the control, set the text it displays, set the coordinates, and call Show method:
var tooltip = new ToolTipLib.ToolTip()
{
Text = "this is a nice toolTip",
LocationX = 100,
LocationY = 200
};
tooltip.Show();
Related
I've a question about transparency in WinForms, but first things first.
I have just created a super-simple application in WinForms with transparent "body". I've only changed the color of the default Form. Designer code:
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.SuspendLayout();
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(192)))), ((int)(((byte)(192)))));
this.ClientSize = new System.Drawing.Size(538, 312);
this.Name = "Form1";
this.Text = "Form1";
this.TransparencyKey = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(192)))), ((int)(((byte)(192)))));
this.ResumeLayout(false);
}
#endregion
}
So in runtime it's basically just a frame + caption. In Form1.cs the only thing I've added was overridden WndProc function where I did:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
const UInt32 WM_NCHITTEST = 0x0084;
const UInt32 WM_MOUSEMOVE = 0x0200;
if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
{
Point screenPoint = new Point(m.LParam.ToInt32());
Point clientPoint = this.PointToClient(screenPoint);
string position = "Position: X=" + clientPoint.X.ToString() + "; Y=" + clientPoint.Y.ToString();
Console.WriteLine(position);
this.Text = position;
}
base.WndProc(ref m);
}
}
In my mind it should work this way: It simply shows mouse pointer position on the window in window's title. BUT, what is super important - position over transparent area should be shown as well. The "Form" inside my application's window is transparent but it's still a part of my application, right? And here comes my question, because not on every machine it works the same.
There are two cases (the same application!!):
1. I can click on stuff under my application (through this transparent area)
2. The window is just transparent. I can't click on stuff like icons or whatever which are under transparent area.
In first case WM_NCHITTEST message is not send at all (over transparent area) in my WndProc overridden function. Transparent area (so the Form) is literally a hole in my app. In second case Form is transparent but in Window's title I can see my pointer position, so simply WM_NCHITTEST message is being send.
Can anyone explain me where could be a problem? This is the same application.
I mean, If you want to create an app with a "hole" or with a "glass" or "window"? You have to control it somehow..
I am working on a migration tool that is primarily a Windows Forms application. What I want to do is provide the ability to run the application as a sort of command line utility where parameters can be passed in and the migration occurs completely void of the GUI. This seems straight forward enough and the entry point for my application looks like this:
[STAThread]
static void Main(string[] args)
{
if (args.Length == 0)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainWindow());
}
else
{
//Command Line Mode
Console.WriteLine("In Command Line Mode");
Console.ReadLine();
}
}
The problem I am running into is that when execution passes into the else block the text is not wrote back to the user at the command prompt which is problematic as I want to update the user at the command prompt as various executions complete. I could easily write a standalone console application but I was hoping to provide a single tool that allowed to different types of entries for a given scenario. Is what I am looking to do possible and, if so, how is it achieved?
Thanks!
Here is a topic that discusses this: http://social.msdn.microsoft.com/forums/en-US/Vsexpressvb/thread/875952fc-cd2c-4e74-9cf2-d38910bde613/
The key is calling AllocConsole function from kernel32.dll
The usual pattern for this would be to write the logic into a class library that you either call from a visual UI or from a command line application.
For example, if on the UI you accepted "Width", "Height" and "Depth" and then calculated volume you would put the calculation into the class library.
So you have either a Console app accepting three arguments, or a forms app with three inputs, and in both cases, they make the same call...
var volumeCalculations = new VolumeCalculations();
var volume = volumeCalculations.GetVolume(width, height, depth);
The console app is very thin and the forms app is very thin, because all they do is get the inputs to pass to the class library.
Here is completed runnable example. Compile with:
csc RunnableForm.cs RunnableForm.Designer.cs
RunnableForm.cs:
using System;
using System.Linq;
using System.Windows.Forms;
namespace Test
{
public partial class RunnableForm : Form
{
public RunnableForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("bang!");
}
[STAThread]
static void Main()
{
string[] args = Environment.GetCommandLineArgs();
// We'll always have one argument (the program's exe is args[0])
if (args.Length == 1)
{
// Run windows forms app
Application.Run(new RunnableForm());
}
else
{
Console.WriteLine("We'll run as a console app now");
Console.WriteLine("Arguments: {0}", String.Join(",", args.Skip(1)));
Console.Write("Enter a string: ");
string str = Console.ReadLine();
Console.WriteLine("You entered: {0}", str);
Console.WriteLine("Bye.");
}
}
}
}
RunnableForm.Designer.cs:
namespace Test
{
partial class RunnableForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(42, 42);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(153, 66);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// RunnableForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 261);
this.Controls.Add(this.button1);
this.Name = "RunnableForm";
this.Text = "RunnableForm";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;
}
}
For colour coding reasons, I need a system whereby I often paste a string to a rich text box (instead of the default standard typing). Unfortunately, it often causes a flash, especially if you keep a key held down.
The RTB doesn't seem to support double buffering, but I'm not sure if that would help anyway. Over-riding the on-paint event also appears ineffective. After researching the web, the best 'solution' I've found so far is to use native Windows interop (LockWindowUpdate etc.). This cured the situation where typing beyond the scroll point was absolutely horrible. Unfortunately, there's still a (lesser) flicker generally now.
The below code is immediately compilable (just create a console project and reference System.Windows.Forms and System.Drawing). If you do, press a key, and keep it held down for say 10 lines worth. If you do, you'll notice more and more flicker cropping up. The more you type, the worse the flicker will become.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace FlickerTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[DllImport("user32.dll")]
public static extern bool LockWindowUpdate(IntPtr hWndLock);
private void rtb_TextChanged(object sender, EventArgs e)
{
String s = rtb.Text;
LockWindowUpdate(rtb.Handle);
rtb.Text = s;
rtb.Refresh(); ////Forces a synchronous redraw of all controls
LockWindowUpdate(IntPtr.Zero);
}
}
//////////////////////////////////////////////////
// Ignore below:
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
partial class Form1
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null)) components.Dispose();
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.rtb = new System.Windows.Forms.RichTextBox();
this.SuspendLayout();
// rtb
this.rtb.BackColor = System.Drawing.Color.Black;
this.rtb.Font = new System.Drawing.Font("Microsoft Sans Serif", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtb.ForeColor = System.Drawing.SystemColors.Window;
this.rtb.Location = new System.Drawing.Point(24, 20);
this.rtb.Name = "rtb";
this.rtb.Size = new System.Drawing.Size(609, 367);
this.rtb.TabIndex = 0;
this.rtb.Text = "";
this.rtb.TextChanged += new System.EventHandler(this.rtb_TextChanged);
// Form1
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1088, 681);
this.Controls.Add(this.rtb);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.RichTextBox rtb;
}
}
You shouldn't need to refresh the RTB for each key-press which is what happens when you handle TextChanged without any Application.DoEvents() call.
I've found the best way to get acceptable performance is to process the KeyDown event instead. If the keycode (with modifier) is a printable character I start a timer whose Tick event handler checks the text of the richtextbox after say 100ms.
Because when the KeyDown event is fired, the typed char hasn't been printed, you can actually modify text-color for this char within this event handler by setting the SelectionColor property (without changing the selection in code), so you don't need to freeze the control. Don't try doing too much processor-intensive stuff in this though as you'll get responsiveness issues.
Finally, modifying RichTextBox behaviour is a sprial, as soon as you deviate from the norm you will start to use custom code for all sorts of tasks (like copy/paste undo/redo and scrolling) that would normally be performed using the controls own features. You then have a decision to make on whether to continue yourself or go for a 3rd party editor. Note also that though you've started using the Windows API already, there will be much more of this, for scrolling, printing and paint events in particular, though there are good online resources documenting these.
Don't let this discourage you, just be aware what the future may hold if your application requirements grow.
I ran into a similar problem recently. The method I chose was to add extension methods to the richtextbox. I like this approach because it's clean, contained, and easily reusable.
This makes pausing the redraw is as simple as
richtextbox.SuspendDrawing();
and resuming
richtextbox.ResumeDrawing()
public static class RichTextBoxExtensions
{
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
private const int WM_SETREDRAW = 0x0b;
public static void SuspendDrawing(this System.Windows.Forms.RichTextBox richTextBox)
{
SendMessage(richTextBox.Handle, WM_SETREDRAW, (IntPtr)0, IntPtr.Zero);
}
public static void ResumeDrawing(this System.Windows.Forms.RichTextBox richTextBox)
{
SendMessage(richTextBox.Handle, WM_SETREDRAW, (IntPtr)1, IntPtr.Zero);
richTextBox.Invalidate();
}
}
I have a problem with the Label control that is terribly flickering.
Below is some code to reproduce the problem.
How to solve this?
UPDATE: The solution for the previous case (a Form contains a Label directly) was to make the form.DoubleBuffered = true. But this is not a generic solution. For example, what should I do in the case of label inside a SplitContainer? this is my real case.
UPDATED CODE:
DoubleBufferedLabel.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace FlickerLabelTest
{
public class DoubleBufferedLabel : Label
{
public DoubleBufferedLabel()
{
DoubleBuffered = true;
}
}
}
DoubleBufferedSplitContainer.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace FlickerLabelTest
{
public class DoubleBufferedSplitContainer : SplitContainer
{
public DoubleBufferedSplitContainer()
{
DoubleBuffered = true;
}
}
}
Form1.cs:
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 FlickerLabelTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
label1.Text += "0";
}
}
}
Form1.Designer.cs:
namespace FlickerLabelTest
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.label1 = new FlickerLabelTest.DoubleBufferedLabel();
this.splitContainer1 = new DoubleBufferedSplitContainer();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
this.SuspendLayout();
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Interval = 1;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// label1
//
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
this.label1.Location = new System.Drawing.Point(0, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(186, 262);
this.label1.TabIndex = 0;
this.label1.Text = "label1";
//
// splitContainer1
//
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer1.Location = new System.Drawing.Point(0, 0);
this.splitContainer1.Name = "splitContainer1";
//
// splitContainer1.Panel2
//
this.splitContainer1.Panel2.Controls.Add(this.label1);
this.splitContainer1.Size = new System.Drawing.Size(284, 262);
this.splitContainer1.SplitterDistance = 94;
this.splitContainer1.TabIndex = 1;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 262);
this.Controls.Add(this.splitContainer1);
this.DoubleBuffered = true;
this.Name = "Form1";
this.Text = "Form1";
this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
this.splitContainer1.Panel2.ResumeLayout(false);
this.splitContainer1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer timer1;
private DoubleBufferedLabel label1;
private DoubleBufferedSplitContainer splitContainer1;
}
}
Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace FlickerLabelTest
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
The problem is with the docking. If you change the Label.Dock from Fill to None, manually set the size of the label to fill the split panel and then anchor it on all sides, it won't flicker.
If you want to see the cause of the flicker, while Dock is still set to Fill, override OnResize in your DoubleBufferedLabel class, start the application, and while it is running set a breakpoint in OnResize. Add a watch for the Size and you'll see it flipping between its design time and runtime sizes.
I tried using a regular SplitContainer and Label in your example, set DoubleBuffer to False on the form, and it did not flicker if I left Dock set to None on the Label.
I think you're looking for this: http://msdn.microsoft.com/en-us/library/3t7htc9c.aspx
example:
class Form1 : Form
{
public Form1() {
this.DoubleBuffered = true;
}
}
Paste this into your form code to help the Dock layout calculations:
protected override void OnLoad(EventArgs e) {
label1.Size = this.ClientSize;
base.OnLoad(e);
}
Not really an answer, but why would you want to update your label each millisecond?
If you meant 1 second, you'd have to set your interval to 1000.
You can solve this by giving the form time to redraw itself and using a larger interval.
Update: turned out, setting DoubleBuffered to true solves the problem. Thanks for csharptest.net for pointing this out and DxCK for correcting me.
Activating Double Buffering on the form will fix the problem. But that is actually an expensive solution. If you just add the following to the form:
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
the flicker will be over too.
The default behavior of the Windows OS it to first let all windows paint their background, and later, let them do actual painting. This is from the old days, when painting letters actually took considerable amount of time. This flag tells it to fold the background paint and the regular paint (for the same window) immediately after each other.
Real Double buffering can be reserved for the cases where you actually do painting yourself (when you override OnPaint).
Stop setting timers to 1ms. No seriously, what you see here is that the Label tries to keep up with it's changes, but fails to do so because they're to frequent. So possible solutions are:
Choose a way to change the Label less often
Activate Double-Buffering on the Form
Why not run your label update function via an asynchronous delegate? Or use System.Threading namespace for a different flavor.
Also, as people before me mentioned, it would be useful if you set the DoubleBuffer property on your form to true (it's no silver bullet though).
I got a business object bounds to a form (each property is bound to a control). There is some business specificatios (such as this field should not be empty, this one must be greater than 0 etc...). What is the best way to check all the rules ?
I currently have a validator on each contorl, so I can check for all validator to be ok, but I don't really like this solution. Indeed the rules are dispached and it's no easy to see all at once.
I can have a big method CheckValidaty that check for all the rules but this leads to a double check with the validators.
What would you do, other solution ?
I would suggest that let the BusinessObject implement IDataErrorInfo. I think it is the cleanest way to handle business errors.
Take a look at these links:
http://msdn.microsoft.com/en-us/library/system.componentmodel.idataerrorinfo_members.aspx
http://www.codegod.de/WebAppCodeGod/objectdatasource-and-idataerrorinfo-with-winforms-AID427.aspx
There are two kinds of validation: data-specific validation (in the persistence layer) and user-interface validation. I prefer to put validation near the input side, because generally you want to show the user what's wrong, and trying to connect data validation to the user interface adds more indirection that has to match the data binding indirection.
Putting data validation in control classes does not seem like a good idea. That basically means the control class can be used only for one specific field.
The standard Windows Forms way of doing things is to put data validation in container. That way the validation can check against the state of other properties and connect the specific control to the ErrorProvider object(s) to display a pertinent error message.
class EmployeeForm : UserControl
{
EmployeeObject employee;
// ...
void employeeNameTextBox_Validating (object sender, CancelEventArgs e)
{
if ( employee.Name.Trim ().Length == 0 ) {
errorProvider.SetError (employeeNameTextBox, "Employee must have a name");
e.Cancel = true;
}
}
void employeeHireDateControl_Validating (...)
{
if ( employee.HireDate < employee.BirthDate ) {
errorProvider.SetError (employeeHireDateControl,
"Employee hire date must be after birth date");
e.Cancel = true;
}
}
}
class ExplorerStyleInterface : ...
{
// ...
bool TryDisplayNewForm (Form oldForm, Form newForm)
{
if ( ! oldForm.ValidateChildren () )
return false;
else {
HideForm (oldForm);
ShowForm (newForm);
return true;
}
}
}
The standard WF way is to fire the Validating event for the specific control when the control loses focus or when ValidateChildren is called on the container (or the container's container). You set up a handler for this event through the event properties for the control on the container; the handler is automatically added to the container.
I'm not sure why this way isn't working for you, unless you don't like the default behavior of refusing to reliquish focus on error, which you can change by setting the AutoValidate property of the container (or the container's container) to EnableAllowFocusChange.
Tell us specifically what you don't like about the standard Windows Forms way of doing things, and maybe we can either offer alternatives or persuade you the standard way will do what you want.
If you have this situation:
- Many controls in the Winform to
validate
- A validation rule for each control
- You want an overall validation within the Save() command
- You don't want validation when controls focus changes
- You also need an red icon showing errors in each control
Then you can copy and paste the following code, that implements a Form with 2 textbox and a Save button:
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 ValidationApp
{
public class ValidationTestForm : Form
{
private TextBox textBox1;
private TextBox textBox2;
private Button btnSave;
private ErrorProvider errorProvider1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.textBox1 = new System.Windows.Forms.TextBox();
this.textBox2 = new System.Windows.Forms.TextBox();
this.btnSave = new System.Windows.Forms.Button();
this.errorProvider1 = new System.Windows.Forms.ErrorProvider(this.components);
((System.ComponentModel.ISupportInitialize)(this.errorProvider1)).BeginInit();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(131, 28);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 0;
//
// textBox2
//
this.textBox2.Location = new System.Drawing.Point(131, 65);
this.textBox2.Name = "textBox2";
this.textBox2.Size = new System.Drawing.Size(100, 20);
this.textBox2.TabIndex = 1;
//
// btnSave
//
this.btnSave.Location = new System.Drawing.Point(76, 102);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(95, 30);
this.btnSave.TabIndex = 2;
this.btnSave.Text = "Save";
this.btnSave.UseVisualStyleBackColor = true;
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
//
// errorProvider1
//
this.errorProvider1.ContainerControl = this;
//
// ValidationTestForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(266, 144);
this.Controls.Add(this.btnSave);
this.Controls.Add(this.textBox2);
this.Controls.Add(this.textBox1);
this.Name = "ValidationTestForm";
this.Text = "ValidationTestForm";
((System.ComponentModel.ISupportInitialize)(this.errorProvider1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
public ValidationTestForm()
{
InitializeComponent();
// path validation
this.AutoValidate = AutoValidate.Disable; // validation to happen only when you call ValidateChildren, not when change focus
this.textBox1.CausesValidation = true;
this.textBox2.CausesValidation = true;
textBox1.Validating += new System.ComponentModel.CancelEventHandler(textBox1_Validating);
textBox2.Validating += new System.ComponentModel.CancelEventHandler(textBox2_Validating);
}
private void textBox1_Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
if (textBox1.Text.Length == 0)
{
e.Cancel = true;
errorProvider1.SetError(this.textBox1, "A value is required.");
}
else
{
e.Cancel = false;
this.errorProvider1.SetError(this.textBox1, "");
}
}
private void textBox2_Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
if (textBox2.Text.Length == 0)
{
e.Cancel = true;
errorProvider1.SetError(this.textBox2, "A value is required.");
}
else
{
e.Cancel = false;
this.errorProvider1.SetError(this.textBox2, "");
}
}
private void btnSave_Click(object sender, EventArgs e)
{
if (this.ValidateChildren()) //will examine all the children of the current control, causing the Validating event to occur on a control
{
// Validated! - Do something then
}
}
}
}
Note:
textBox1_Validating, textBox2_Validating...do the rule check
What is wrong with the validator approach? It's quite acceptable, and you can write your own, to implement your own rules.