I would like to display an error provider icon over a button at certain times, like this:
However, I also don't want the icon to obscure/prevent the button click if the user happens to click on that when intending to click on the button, perhaps because the error message on hover says something like "click here to fix".
ErrorProvider has no events (well it has one irrelevant one). I tried using the methodology described in this post, but the problem is that no pre-filter message even occurs when I click on the error icon. Here's my sample code:
public partial class Form1 : Form, IMessageFilter
{
public Form1()
{
InitializeComponent();
errorProvider1.SetIconPadding(button1, -27);
errorProvider1.SetError(button1, "error");
Application.AddMessageFilter(this);
}
public bool PreFilterMessage(ref Message m)
{
var ctrl = Control.FromHandle(m.HWnd);
var msg = m.Msg;
// the values in the "ignore" list below are all movement/paint events
if (ctrl is Button b && (!(new[] { 15, 512, 675, 280, 673 }.Any((v) => msg == v))))
{
// only gets here when un-obscured button surface is clicked.
}
// this only ever prints form and button
if (ctrl != null) System.Diagnostics.Debug.Print($"{ctrl.GetType().Name}");
return false;
}
}
Is there any way to do this?
As you want to display the error icon inside the button, you can easily fake the ErrorProvider
by drawing the icon manually and using a ToolTip
explicitly:
public partial class FakeErrorProvider : Form
{
private static Icon _errorIcon = new ErrorProvider().Icon;
private static int _errorIconPadding = 4;
private ToolTip _toolTip;
private bool _hasError;
public FakeErrorProvider()
{
InitializeComponent();
_toolTip = new ToolTip();
button1.Click += Button1_Click;
button1.Paint += Button1_Paint;
button1.MouseMove += Button1_MouseMove;
}
private void Button1_Click(object? sender, EventArgs e)
{
// fixing the error only when the error icon is clicked
if (!_hasError || GetErrorRectangle(button1).Contains(button1.PointToClient(Cursor.Position)))
_hasError = !_hasError;
button1.Invalidate();
if (!_hasError)
_toolTip.SetToolTip(button1, String.Empty);
}
private void Button1_Paint(object? sender, PaintEventArgs e)
{
// showing the error icon
if (_hasError)
e.Graphics.DrawIcon(_errorIcon, GetErrorRectangle(button1));
}
private void Button1_MouseMove(object? sender, MouseEventArgs e)
{
// displaying the error tooltip only when the cursor is above the error icon
bool isVisible = _toolTip.GetToolTip(button1).Length > 0;
if (_hasError && !isVisible && GetErrorRectangle(button1).Contains(e.Location))
_toolTip.SetToolTip(button1, "click here to fix");
else if (isVisible && (!_hasError || !GetErrorRectangle(button1).Contains(e.Location)))
_toolTip.SetToolTip(button1, String.Empty);
}
private Rectangle GetErrorRectangle(Button button)
{
var rect = new Rectangle(Point.Empty, _errorIcon.Size);
rect.Offset(button.Width - rect.Width - _errorIconPadding, button.Height / 2 - rect.Height / 2);
return rect;
}
}
And the result:
Edit:
If clicking any part of the button should fix the issue, the solution is even simpler because you don't need to bother with the icon area at all and the tooltip can be displayed when you hover any part of the button just like a normal tooltip.
Just set button1.TextImageRelation = TextImageRelation.TextBeforeImage
, and then simply adjust button1.Image
along with the tooltip.