create custom object (combination of two objects) - c#

hello creating a custom object may be a widely published topic, but my lack of coding skills proves problematic in actually implementing what i'm trying to do.
in a nutshell i'm adding controls at runtime in a flowpanelLayout. right now it's just listboxes, that code is all working fine. i would like a way to label the listboxes that are getting added, i can't think of a better way to do this than to use a text label. i was thinking it would be slick to create some sort of custom control (if possible) which is a listbox and a textlabel like one above the other or something. this way i can add the new custom control in my current code and assign the listbox attributes and label text, etc all in one motion.
this is what i was thinking, maybe there's even a better way to do this.
my current listview creation code:
public void addListView()
{
ListView newListView = new ListView();
newListView.AllowDrop = true;
newListView.DragDrop += listView_DragDrop;
newListView.DragEnter += listView_DragEnter;
newListView.MouseDoubleClick += listView_MouseDoubleClick;
newListView.MouseDown += listView_MouseDown;
newListView.DragOver += listView_DragOver;
newListView.Width = 200;
newListView.Height = 200;
newListView.View = View.Tile;
newListView.MultiSelect = false;
flowPanel.Controls.Add(newListView);
numWO++;
numberofWOLabel.Text = numWO.ToString();
}
maybe the actual best answer is simply to also add a textlabel here and define some set coordinates to put it. let me know what you think.
if a custom control is the way to go, please provide some resource or example for me - i'd appreciate it.

Here is a custom user control that can do that:
You just need to set TitleLabelText to set the title.
[Category("Custom User Controls")]
public class ListBoxWithTitle : ListBox
{
private Label titleLabel;
public ListBoxWithTitle()
{
this.SizeChanged +=new EventHandler(SizeSet);
this.LocationChanged +=new EventHandler(LocationSet);
this.ParentChanged += new EventHandler(ParentSet);
}
public string TitleLabelText
{
get;
set;
}
//Ensures the Size, Location and Parent have been set before adding text
bool isSizeSet = false;
bool isLocationSet = false;
bool isParentSet = false;
private void SizeSet(object sender, EventArgs e)
{
isSizeSet = true;
if (isSizeSet && isLocationSet && isParentSet)
{
PositionLabel();
}
}
private void LocationSet(object sender, EventArgs e)
{
isLocationSet = true;
if (isSizeSet && isLocationSet && isParentSet)
{
PositionLabel();
}
}
private void ParentSet(object sender, EventArgs e)
{
isParentSet = true;
if (isSizeSet && isLocationSet && isParentSet)
{
PositionLabel();
}
}
private void PositionLabel()
{
//Initializes text label
titleLabel = new Label();
//Positions the text 10 pixels below the Listbox.
titleLabel.Location = new Point(this.Location.X, this.Location.Y + this.Size.Height + 10);
titleLabel.AutoSize = true;
titleLabel.Text = TitleLabelText;
this.Parent.Controls.Add(titleLabel);
}
}
Example use:
public Form1()
{
InitializeComponent();
ListBoxWithTitle newitem = new ListBoxWithTitle();
newitem.Size = new Size(200, 200);
newitem.Location = new Point(20, 20);
newitem.TitleLabelText = "Test";
this.Controls.Add(newitem);
}

Related

Well-designed Expandable TextBoxes in an Expandable Panel

I have records in my .NET WinForms app that I lay out with enhanced TextBox controls on panels when the records are editable, but I set the TextBoxes to ReadOnly when the records are not editable. Clicking the save button on an editable record saves the text to the database, and then it is displayed as an un-editable record (until the edit button is clicked). Please see the following screen grab:
As you can hopefully see, the first record is not editable, but the second one is. The problem I have is that I would like the TextBox to grow in Height if the text is too much to fit. It seems that the TextBox is doing the WordWrap, but it either only shows one line of the text or only the first two. Something is always cut off at the bottom.
I have looked at several other posts on this site, including, especially, Expandable WinForms TextBox.
Here is some sample code for the panel:
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
...
Field1 = new ExpandoField { Multiline = true, WordWrap = true };
Field1.Location = new System.Drawing.Point(42, 3);
if (CanEdit)
{
Field1.BackColor = System.Drawing.Color.White;
Field1.TabIndex = 20;
}
else
{
((ExpandoField) Field1).ReadOnly = true;
Field1.ForeColor = System.Drawing.Color.FromArgb(0, 50, 0);
Field1.BackColor = System.Drawing.Color.Snow;
Field1.TabIndex = 0;
Field1.TabStop = false;
}
Field1.Text = Text1;
Field1.Dock = DockStyle.None;
Field1.Size = new System.Drawing.Size(538 - 25, 34);
Field1.MinimumSize = Field1.Size;
Field1.AutoSize = true;
Controls.Add(Field1);
As you can see, I have AutoSize set to true for the panel. The code for Field2 is similar to Field1.
ExpandoField is based on sample code I saw from a response by dstran in Expandable WinForms TextBox. It seemed to be the most complete implementation of the suggestion marked as the answer to that post. Here's the code:
class ExpandoField : TextBox
{
private double m_growIndex = 0.0;
private Timer m_timer;
public ExpandoField()
{
AutoSize = false;
this.Height = 20;
// Without the timer, I got a lot of AccessViolationException in the System.Windows.Forms.dll.
m_timer = new Timer();
m_timer.Interval = 1;
m_timer.Enabled = false;
m_timer.Tick += new EventHandler(m_timer_Tick);
this.KeyDown += new KeyEventHandler(ExpandoField_KeyDown);
}
void ExpandoField_KeyDown(object sender, KeyEventArgs e)
{
if (e.Modifiers == Keys.Control && e.KeyCode == Keys.A)
this.SelectAll();
}
void m_timer_Tick(object sender, EventArgs e)
{
var sz = new System.Drawing.Size(Width, Int32.MaxValue);
sz = TextRenderer.MeasureText(Text, Font, sz, TextFormatFlags.TextBoxControl);
m_growIndex = (double)(sz.Width / (double)Width);
if (m_growIndex > 0)
Multiline = true;
else
Multiline = false;
int tempHeight = (int) (20 * m_growIndex);
if (tempHeight <= 20)
Height = 20;
else
Height = tempHeight;
m_timer.Enabled = false;
}
public override sealed bool AutoSize
{
get { return base.AutoSize; }
set { base.AutoSize = value; }
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
m_timer.Enabled = true;
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
m_timer.Enabled = true;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
m_timer.Enabled = true;
}
}
This is obviously not quite working. I have the panel set to AutoSize, but it is not growing to accomodate the second TextBox. Also, I need to somehow push the second TextBox down when the first one grows. Is there some good way for the panel to know when ExpandoField gets an OnSizeChanged event? It seems like the growth of that panel would then need to cause the remainder of the list of panels to be redrawn in lower locations. I'm not sure how to get this cascade effect to work right...
I also think the use of the timer seems like an inefficient kluge...
I'm still learning WinForms. Is there some well-designed way I can get the behavior that I want? Is there some event I can catch when the WordWrap takes place (or when the text exceeds the size of the TextBox)? That would allow me to resize the TextBox. And how does the TextBox let the panel know that it has changed? Does it need to call the OnSizeChanged handler for it's parent panel? Does the panel need to call the OnSizeChanged handler for it's parent list?
Any suggestions?
I believe I have the answer, after 3 or 4 failed attempts...
class ExpandoField : TextBox
{
private bool UpdateInProgress = false;
private static System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(#"\r\n");
public delegate void CallbackFn();
CallbackFn VSizeChangedCallback;
public ExpandoField(CallbackFn VSizeChanged)
{
AutoSize = false;
VSizeChangedCallback = VSizeChanged;
this.KeyDown += new KeyEventHandler(ExpandoField_KeyDown);
}
public void ExpandoField_KeyDown(object sender, KeyEventArgs e)
{
if (e.Modifiers == Keys.Control && e.KeyCode == Keys.A)
this.SelectAll();
}
public void UpdateSize()
{
if (UpdateInProgress == false && Text.Length > 0)
{
UpdateInProgress = true;
int numLines = 0;
System.Drawing.Size baseSize = new System.Drawing.Size(Width, Int32.MaxValue);
System.Drawing.Size lineSize = baseSize; // compiler thinks we need something here...
// replace CR/LF with single character (paragraph mark 'ΒΆ')
string tmpText = rgx.Replace(Text, "\u00B6");
// split text at paragraph marks
string[] parts = tmpText.Split(new char[1] { '\u00B6' });
numLines = parts.Count();
foreach (string part in parts)
{
// if the width of this line is greater than the width of the text box, add needed lines
lineSize = TextRenderer.MeasureText(part, Font, baseSize, TextFormatFlags.TextBoxControl);
numLines += (int) Math.Floor(((double) lineSize.Width / (double) Width));
}
if (numLines > 1)
Multiline = true;
else
Multiline = false;
int tempHeight = Margin.Top + (lineSize.Height * numLines) + Margin.Bottom;
if (tempHeight > Height || // need to grow...
Height - tempHeight > lineSize.Height) // need to shrink...
{
Height = tempHeight;
VSizeChangedCallback();
}
UpdateInProgress = false;
}
}
public override sealed bool AutoSize
{
get { return base.AutoSize; }
set { base.AutoSize = value; }
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
UpdateSize();
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
UpdateSize();
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
UpdateSize();
}
}
Note that on the constructor this subclass of TextBox now accepts a delegate callback to let the parent class know that the TextBox has changed its size. (I suppose I should have handled the possibility of a null value here...)
Thankfully, this solution no longer required a timer.
I have tested this code pretty well, and I have watched it both grow & shrink. It respects MaximumSize, and it even handles the presence of carriage-return/line-feed pairs. (This code assumes Windows; it should be trivial to modify it for Linux, etc.) Feel free to suggest improvements.

Sliding panels - one works, the others don't

I've run into a bit of a wall and I don't know how I've managed to stuff it up. I'm trying to have multiple panels on my application in C# and each slides in and out from the menu along the side. I've written a separate slide class:
class Slide
{
Panel pane;
Button btn;
bool hidden;
Timer t;
const int maxWidth = 315;
public Slide(Panel p, Button b)
{
this.pane = p;
this.btn = b;
hidden = true;
btn.Click += new EventHandler(btnClick);
t = new Timer();
t.Interval = 15;
t.Tick += new EventHandler(timeTick);
}
private void timeTick(object sender, EventArgs e)
{
if(hidden)
{
SlidingPane(+10);
}
else
{
SlidingPane(-10);
}
}
private void btnClick(object sender, EventArgs e)
{
t.Start();
}
private void SlidingPane(int i)
{
pane.Width += i;
if(pane.Width >= maxWidth || pane.Width <= 0)
{
t.Stop();
hidden = !hidden;
}
}
}
And I've initialised the panels as follows:
Slide menuP, calendarP, peopleP, taskP, settingsP;
public Form1()
{
InitializeComponent();
ButtonColours();
InitialisePanes();
}
private void InitialisePanes()
{
menuP = new Slide(menuPane, menuButton);
calendarP = new Slide(calendarPane, calendarButton);
peopleP = new Slide(peoplePane, peopleButton);
taskP = new Slide(taskPane, toDoButton);
settingsP = new Slide(settingsPane, settingsButton);
}
And here's the Form designer code for the working panel:
this.menuPane.BackColor = System.Drawing.Color.SlateGray;
this.menuPane.Controls.Add(this.peoplePane);
this.menuPane.Dock = System.Windows.Forms.DockStyle.Left;
this.menuPane.Location = new System.Drawing.Point(67, 0);
this.menuPane.Name = "menuPane";
this.menuPane.Size = new System.Drawing.Size(0, 652);
this.menuPane.TabIndex = 2;
And the others are exactly the same. Eg:
this.peoplePane.BackColor = System.Drawing.Color.SlateGray;
this.peoplePane.Controls.Add(this.calendarPane);
this.peoplePane.Dock = System.Windows.Forms.DockStyle.Left;
this.peoplePane.Location = new System.Drawing.Point(67, 0);
this.peoplePane.Name = "peoplePane";
this.peoplePane.Size = new System.Drawing.Size(0, 652);
this.peoplePane.TabIndex = 2;
I've started up my application and I click on the menuButton, it works. Slides in and out beautifully. I click on the others and....nothing happens.
Can anyone see why this is happening? Everything I'm looking at tells me that it should be working.
To make sure all panes are correctly aligned with each other and (not) nested you could use code like this:
foreach( Control ctl in new[] { peoplePane, calendarPane, taskPane, settingsPane })
{
ctl.Parent = menuPane.Parent;
ctl.Location = menuPane.Location;
}
It assumes that menuPane is at the right spot and makes all others sit right on top without nesting them.
The code you posted contained incorrect nesting and moving panels to the same spot with the mouse will also create (in this case unwanted) nesting. Moving with the keyboard avoids it but is tedious.

Trying to make a custom print class where the application tells when to print a page

I'm building a series of apps for my place of business so I'm trying to create my own printing class that I can refer to for all of my applications.
The issue is, I'm trying to figure out a way for the application to tell the class when to print the page, and I am unable to find a way to do so.
For example, this is what I have so far:
// Report Variables
private bool bPrinting = false;
private int iPage = 0;
private float fOverflow = 0.00F;
private string sPrintLine = null;
private Font fontTmpFont = null;
private PrintPageEventArgs ppeaEv = null;
private Margins mMargins = new System.Drawing.Printing.Margins(25, 25, 25, 25); // Set wide margins
// Clear the print line
public void LineClear()
{
sPrintLine = null;
}
// Insert a string into the print line at the specified position within the line
public void LineInsert(string _InsertString, int _InsertPosition)
{
if (sPrintLine.Length <= _InsertPosition)
sPrintLine = sPrintLine.PadLeft(_InsertPosition) + _InsertString;
else if (sPrintLine.Length <= (_InsertPosition + _InsertString.Length))
sPrintLine = sPrintLine.Substring(0, _InsertPosition) + _InsertString;
else
sPrintLine = sPrintLine.Substring(0, _InsertPosition) + _InsertString + sPrintLine.Substring(_InsertPosition + _InsertString.Length);
}
// Check to see if the line we're trying to print is at the end of the page
public bool AtEndOfPage()
{
return AtEndOfPage(new Font("Courier", 10));
}
public bool AtEndOfPage(Font _Font)
{
if ((fOverflow + _Font.GetHeight(ppeaEv.Graphics)) > ppeaEv.MarginBounds.Height)
return true;
else
return false;
}
// Attempt to print the line
public void LinePrint()
{
LinePrint(null, null);
}
public void LinePrint(Font _Font)
{
LinePrint(_Font, null);
}
public void LinePrint(Font _Font, Brush _Color)
{
if (_Font == null)
_Font = new Font("Courier", 10);
if (_Color == null)
_Color = Brushes.Black;
ppeaEv.Graphics.DrawString(sPrintLine, _Font, _Color,
ppeaEv.MarginBounds.Left, ppeaEv.MarginBounds.Top + fOverflow,
new StringFormat()); // 'Draw' line on page
fOverflow += _Font.GetHeight(ppeaEv.Graphics);
}
// We are done with the report, tell the Print Service to finish up
public void EndReport()
{
ppeaEv.HasMorePages = false;
}
// This is what gets called when the user clicks on 'Print'
private void Print_Click(object sender, EventArgs e)
{
PrintDocument printDocument = new PrintDocument();
printDocument.DefaultPageSettings.Margins = mMargins; // Set margins for pages
printDocument.DefaultPageSettings.Landscape = false; // Set Portrait mode
printDocument.BeginPrint += new PrintEventHandler(printDocument_BeginPrint);
printDocument.EndPrint += new PrintEventHandler(printDocument_EndPrint);
PrintDialog printDialog = new PrintDialog();
printDialog.Document = printDocument; // Set the Document for this print dialog
printDialog.AllowSomePages = true; // Allow the user to select only some pages to print
printDialog.ShowHelp = true; // Allow help button
DialogResult result = printDialog.ShowDialog();
if (result == DialogResult.OK)
{
printDocument.Print(); // Raises PrintPage event
}
printDocument.Dispose();
printDialog.Dispose();
}
void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
iPage = 0;
fOverflow = 0.00F;
}
void printDocument_EndPrint(object sender, PrintEventArgs e)
{
}
As you may be able to tell, I'm trying to fill the page externally to the class, and then have the app tell the class when to print the page when "AtEndOfPage"
I know about "printDocument.PrintPage", but that doesn't seem to be what I need; It builds the page internal to the method and then prints it. I'm not going to be building the print page within that method.
I also am trying to allow for multiple fonts on a single page.
Is there a way to do this?
Thank you all in advance,
Robert

Accessing control from a different function in Winforms

I have a hopefully simple question, for which I could not google up any solution:
I would like to add labels buttons textboxes at runtime, which i can in the constructor of my Form, but I cannot access them outside of the constructor.
Something like this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Label changeMe = new Label();
changeMe.AutoSize = true;
changeMe.Left = 50; changeMe.Top = 50;
changeMe.Text = "Change this text from other function";
changeMe.IsAccessible = true;
//changeMe.Name = "changeMe";
this.Controls.Add(changeMe);
}
private void btn_changeLabelText_Click(object sender, EventArgs e)
{
//Would like to achieve this:
//changeMe.Text = "You changed me !! ";
//But I found only this solution:
//Label l; l = (Label)this.Controls.Find("changeMe", true)[0]; l.Text = "You changed Me";
}
}
The solution I commented out is the only one I found, but I can't believe there isn't any better one then that. Is there a way to make my controls public for example? Whats a good way of solving this problem?
(The number of controls vary on every time I call my Dialogbox I am trying to design)
Thanks
EDIT --------------------------
After accepting Adil's answer, I stayed with the following solution, which I only find better as the originally outcommented this.Control.Find way, because I also want to have "n" Textboxes, and in thie way I can easily loop through them and read inputs.
public partial class Form1 : Form
{
public struct labels { public Label lbl; public int id; }
List<labels> lbls = new List<labels>();
public Form1()
{
InitializeComponent();
Label changeMe = new Label();
changeMe.AutoSize = true;
changeMe.Left = 50; changeMe.Top = 50;
changeMe.Text = "Change this text from other function";
changeMe.IsAccessible = true;
this.Controls.Add(changeMe);
labels newlabel = new labels();
newlabel.id = 137; newlabel.lbl = changeMe;
lbls.Add(newlabel);
}
private void btn_changeLabelText_Click(object sender, EventArgs e)
{
lbls.Find(i => i.id == 137).lbl.Text = "You changed me";
}
}
You declared the label inside constructor that makes it accessible only in constructor, ceclare label out side constructor at class scope as class member.
Label changeMe = new Label();
public Form1()
{
InitializeComponent();
Label changeMe = new Label();
changeMe.AutoSize = true;
changeMe.Left = 50; changeMe.Top = 50;
changeMe.Text = "Change this text from other function";
changeMe.IsAccessible = true;
//changeMe.Name = "changeMe";
this.Controls.Add(changeMe);
}

Displaying tooltip over a disabled control

I'm trying to display a tooltip when mouse hovers over a disabled control. Since a disabled control does not handle any events, I have to do that in the parent form. I chose to do this by handling the MouseMove event in the parent form. Here's the code that does the job:
void Form1_MouseMove(object sender, MouseEventArgs e)
{
m_toolTips.SetToolTip(this, "testing tooltip on " + DateTime.Now.ToString());
string tipText = this.m_toolTips.GetToolTip(this);
if ((tipText != null) && (tipText.Length > 0))
{
Point clientLoc = this.PointToClient(Cursor.Position);
Control child = this.GetChildAtPoint(clientLoc);
if (child != null && child.Enabled == false)
{
m_toolTips.ToolTipTitle = "MouseHover On Disabled Control";
m_toolTips.Show(tipText, this, 10000);
}
else
{
m_toolTips.ToolTipTitle = "MouseHover Triggerd";
m_toolTips.Show(tipText, this, 3000);
}
}
}
The code does handles the tooltip display for the disabled control. The problem is that when mouse hovers over a disabled control, the tooltip keeps closing and redisplay again. From the display time I added in the tooltip, when mouse is above the parent form, the MouseMove event gets called roughly every 3 seconds, so the tooltip gets updated every 3 seconds. But when mouse is over a disabled control, the tooltip refreshes every 1 second. Also, when tooltip refreshes above form, only the text gets updated with a brief flash. But when tooltip refreshes above a disabled control, the tooltip windows closes as if mouse is moving into a enabled control and the tooltip is supposed to be closed. but then the tooltip reappears right away.
Can someone tell me why is this? Thanks.
you can show the tooltip only once when mouse hits the disbled control and then hide it when mouse leaves it. Pls, take a look at the code below, it should be showing a tooltip message for all the disabled controls on the form
private ToolTip _toolTip = new ToolTip();
private Control _currentToolTipControl = null;
public Form1()
{
InitializeComponent();
_toolTip.SetToolTip(this.button1, "My button1");
_toolTip.SetToolTip(this.button2, "My button2");
_toolTip.SetToolTip(this.textBox1, "My text box");
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
Control control = GetChildAtPoint(e.Location);
if (control != null)
{
if (!control.Enabled && _currentToolTipControl == null)
{
string toolTipString = _toolTip.GetToolTip(control);
// trigger the tooltip with no delay and some basic positioning just to give you an idea
_toolTip.Show(toolTipString, control, control.Width/2, control.Height/2);
_currentToolTipControl = control;
}
}
else
{
if (_currentToolTipControl != null) _toolTip.Hide(_currentToolTipControl);
_currentToolTipControl = null;
}
}
hope this helps, regards
The answer turned out to be a bit simpler, but needed to be applied at all times.
void OrderSummaryDetails_MouseMove(object sender, MouseEventArgs e)
{
Control control = GetChildAtPoint(e.Location);
if (control != null)
{
string toolTipString = mFormTips.GetToolTip(control);
this.mFormTips.ShowAlways = true;
// trigger the tooltip with no delay and some basic positioning just to give you an idea
mFormTips.Show(toolTipString, control, control.Width / 2, control.Height / 2);
}
}
In case of TextBox control, making it as readonly solved the issue.
I tried many but ended up using this simple trick which I think it is more effective.
Create a subclass(CustomControl with just base control in it) which extends UserControl
then instead of setting "Enabled" property to false create a Method which disables just basecontrol in it instead of whole CustomControl.
Set the tool tip on CustomControl still will be able to fire eventhandlers setting the basecontrol disabled. This works wherever CustomControl is in use rather than coding on every form you use with.
Here is the hint.. :)
public partial class MyTextBox : UserControl
{
...
...
...
public void DisableMyTextBox()
{
this.txt.Enabled = false; //txt is the name of Winform-Textbox from my designer
this.Enabled = true;
}
public void EnableMyTextBox()
{
this.txt.Enabled = true;
this.Enabled = true;
}
//set the tooltip from properties tab in designer or wherever
}
Since no one ever pointed this out, this works for any control that exposes ToolTipService:
ToolTipService.ShowOnDisabled="True"
As in this example:
<Button Content="OK"
ToolTipService.ShowOnDisabled="True" />
/*
Inspired by the suggestions above in this post, i wrapped it up as an extended ToolTip control specially works for disabled control.
// Reference example
var td = new ToolTipOnDisabledControl();
this.checkEdit3.Enabled = false;
td.SetTooltip(this.checkEdit3, "tooltip for disabled 3333333333333");
*/
using System;
using System.Windows.Forms;
namespace TestApp1
{
public class ToolTipOnDisabledControl
{
#region Fields and Properties
private Control enabledParentControl;
private bool isShown;
public Control TargetControl { get; private set; }
public string TooltipText { get; private set; }
public ToolTip ToolTip { get; }
#endregion
#region Public Methods
public ToolTipOnDisabledControl()
{
this.ToolTip = new ToolTip();
}
public void SetToolTip(Control targetControl, string tooltipText = null)
{
this.TargetControl = targetControl;
if (string.IsNullOrEmpty(tooltipText))
{
this.TooltipText = this.ToolTip.GetToolTip(targetControl);
}
else
{
this.TooltipText = tooltipText;
}
if (targetControl.Enabled)
{
this.enabledParentControl = null;
this.isShown = false;
this.ToolTip.SetToolTip(this.TargetControl, this.TooltipText);
return;
}
this.enabledParentControl = targetControl.Parent;
while (!this.enabledParentControl.Enabled && this.enabledParentControl.Parent != null)
{
this.enabledParentControl = this.enabledParentControl.Parent;
}
if (!this.enabledParentControl.Enabled)
{
throw new Exception("Failed to set tool tip because failed to find an enabled parent control.");
}
this.enabledParentControl.MouseMove += this.EnabledParentControl_MouseMove;
this.TargetControl.EnabledChanged += this.TargetControl_EnabledChanged;
}
public void Reset()
{
if (this.TargetControl != null)
{
this.ToolTip.Hide(this.TargetControl);
this.TargetControl.EnabledChanged -= this.TargetControl_EnabledChanged;
this.TargetControl = null;
}
if (this.enabledParentControl != null)
{
this.enabledParentControl.MouseMove -= this.EnabledParentControl_MouseMove;
this.enabledParentControl = null;
}
this.isShown = false;
}
#endregion
#region Private Methods
private void EnabledParentControl_MouseMove(object sender, MouseEventArgs e)
{
if (e.Location.X >= this.TargetControl.Left &&
e.Location.X <= this.TargetControl.Right &&
e.Location.Y >= this.TargetControl.Top &&
e.Location.Y <= this.TargetControl.Bottom)
{
if (!this.isShown)
{
this.ToolTip.Show(this.TooltipText, this.TargetControl, this.TargetControl.Width / 2, this.TargetControl.Height / 2, this.ToolTip.AutoPopDelay);
this.isShown = true;
}
}
else
{
this.ToolTip.Hide(this.TargetControl);
this.isShown = false;
}
}
private void TargetControl_EnabledChanged(object sender, EventArgs e)
{
if (TargetControl.Enabled)
{
TargetControl.EnabledChanged -= TargetControl_EnabledChanged;
enabledParentControl.MouseMove -= EnabledParentControl_MouseMove;
}
}
#endregion
}
}
Here is how I solved this problem
I have an application that generates code automatically for a PIC32MX.
The application has 3 Tab Pages text = PWM, ADC and UART.
On each Tab Page I have one Check Box text = RPA0
The intention is, when a peripheral uses RPA0, the other peripheral is prevented
from using that pin, by disabling it on the other pages, and a tooltip text must pop up
on the disabled check boxs saying (example "Used by PWM")
what peripheral is using that pin.
The problem is that the tooltip text won't pop up on a disabled check box.
To solve the problem, I just removed the text of the check boxes and inserted labels with the text the check box should have.
When a check box is checked, the other check boxes are disabled and the label next to it takes a tool tip text.
As the label is enabled, the tooltip text pops up, even on a disabled check box.
Double the work, half the complexity.
Here is the code and the designer for C# 2010
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 WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void cb_ADC_RPA0_CheckedChanged(object sender, EventArgs e)
{
/* Disable pin on other peripherals */
cb_UART_RPA0.Enabled = !((CheckBox)sender).Checked;
cb_PWM_RPA0.Enabled = !((CheckBox)sender).Checked;
SetTootTip((CheckBox)sender, lbl_PWM_RPA0, lbl_UART_RPA0, "ADC");
}
private void cb_PWM_RPA0_CheckedChanged(object sender, EventArgs e)
{
/* Disable pin on other peripherals */
cb_UART_RPA0.Enabled = !((CheckBox)sender).Checked;
cb_ADC_RPA0.Enabled = !((CheckBox)sender).Checked;
SetTootTip((CheckBox)sender, lbl_ADC_RPA0, lbl_UART_RPA0, "PWM");
}
private void cb_UART_RPA0_CheckedChanged(object sender, EventArgs e)
{
/* Disable pin on other peripherals */
cb_ADC_RPA0.Enabled = !((CheckBox)sender).Checked;
cb_PWM_RPA0.Enabled = !((CheckBox)sender).Checked;
SetTootTip((CheckBox)sender, lbl_ADC_RPA0, lbl_PWM_RPA0, "UART");
}
void SetTootTip(CheckBox sender, Label lbl1, Label lbl2, string text)
{
/* Update tooltip on the other labels */
if (sender.Checked)
{
toolTip1.SetToolTip(lbl1, "Used by " + text);
toolTip1.SetToolTip(lbl2, "Used by " + text);
}
else
{
toolTip1.SetToolTip(lbl1, "");
toolTip1.SetToolTip(lbl2, "");
}
}
}
}
namespace WindowsFormsApplication1
{
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.tabControl1 = new System.Windows.Forms.TabControl();
this.tpPWM = new System.Windows.Forms.TabPage();
this.tpUART = new System.Windows.Forms.TabPage();
this.tpADC = new System.Windows.Forms.TabPage();
this.cb_PWM_RPA0 = new System.Windows.Forms.CheckBox();
this.cb_ADC_RPA0 = new System.Windows.Forms.CheckBox();
this.lbl_PWM_RPA0 = new System.Windows.Forms.Label();
this.lbl_ADC_RPA0 = new System.Windows.Forms.Label();
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
this.lbl_UART_RPA0 = new System.Windows.Forms.Label();
this.cb_UART_RPA0 = new System.Windows.Forms.CheckBox();
this.tabControl1.SuspendLayout();
this.tpPWM.SuspendLayout();
this.tpUART.SuspendLayout();
this.tpADC.SuspendLayout();
this.SuspendLayout();
//
// tabControl1
//
this.tabControl1.Controls.Add(this.tpPWM);
this.tabControl1.Controls.Add(this.tpUART);
this.tabControl1.Controls.Add(this.tpADC);
this.tabControl1.Location = new System.Drawing.Point(12, 12);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(629, 296);
this.tabControl1.TabIndex = 0;
//
// tpPWM
//
this.tpPWM.Controls.Add(this.lbl_PWM_RPA0);
this.tpPWM.Controls.Add(this.cb_PWM_RPA0);
this.tpPWM.Location = new System.Drawing.Point(4, 22);
this.tpPWM.Name = "tpPWM";
this.tpPWM.Padding = new System.Windows.Forms.Padding(3);
this.tpPWM.Size = new System.Drawing.Size(621, 270);
this.tpPWM.TabIndex = 0;
this.tpPWM.Text = "PWM";
this.tpPWM.UseVisualStyleBackColor = true;
//
// tpUART
//
this.tpUART.Controls.Add(this.cb_UART_RPA0);
this.tpUART.Controls.Add(this.lbl_UART_RPA0);
this.tpUART.Location = new System.Drawing.Point(4, 22);
this.tpUART.Name = "tpUART";
this.tpUART.Padding = new System.Windows.Forms.Padding(3);
this.tpUART.Size = new System.Drawing.Size(621, 270);
this.tpUART.TabIndex = 1;
this.tpUART.Text = "UART";
this.tpUART.UseVisualStyleBackColor = true;
//
// tpADC
//
this.tpADC.Controls.Add(this.lbl_ADC_RPA0);
this.tpADC.Controls.Add(this.cb_ADC_RPA0);
this.tpADC.Location = new System.Drawing.Point(4, 22);
this.tpADC.Name = "tpADC";
this.tpADC.Padding = new System.Windows.Forms.Padding(3);
this.tpADC.Size = new System.Drawing.Size(621, 270);
this.tpADC.TabIndex = 2;
this.tpADC.Text = "ADC";
this.tpADC.UseVisualStyleBackColor = true;
//
// cb_PWM_RPA0
//
this.cb_PWM_RPA0.AutoSize = true;
this.cb_PWM_RPA0.Location = new System.Drawing.Point(17, 65);
this.cb_PWM_RPA0.Name = "cb_PWM_RPA0";
this.cb_PWM_RPA0.Size = new System.Drawing.Size(15, 14);
this.cb_PWM_RPA0.TabIndex = 0;
this.cb_PWM_RPA0.UseVisualStyleBackColor = true;
this.cb_PWM_RPA0.CheckedChanged += new System.EventHandler(this.cb_PWM_RPA0_CheckedChanged);
//
// cb_ADC_RPA0
//
this.cb_ADC_RPA0.AutoSize = true;
this.cb_ADC_RPA0.Location = new System.Drawing.Point(17, 65);
this.cb_ADC_RPA0.Name = "cb_ADC_RPA0";
this.cb_ADC_RPA0.Size = new System.Drawing.Size(15, 14);
this.cb_ADC_RPA0.TabIndex = 1;
this.cb_ADC_RPA0.UseVisualStyleBackColor = true;
this.cb_ADC_RPA0.CheckedChanged += new System.EventHandler(this.cb_ADC_RPA0_CheckedChanged);
//
// lbl_PWM_RPA0
//
this.lbl_PWM_RPA0.AutoSize = true;
this.lbl_PWM_RPA0.Location = new System.Drawing.Point(38, 65);
this.lbl_PWM_RPA0.Name = "lbl_PWM_RPA0";
this.lbl_PWM_RPA0.Size = new System.Drawing.Size(35, 13);
this.lbl_PWM_RPA0.TabIndex = 1;
this.lbl_PWM_RPA0.Text = "RPA0";
//
// lbl_ADC_RPA0
//
this.lbl_ADC_RPA0.AutoSize = true;
this.lbl_ADC_RPA0.Location = new System.Drawing.Point(38, 66);
this.lbl_ADC_RPA0.Name = "lbl_ADC_RPA0";
this.lbl_ADC_RPA0.Size = new System.Drawing.Size(35, 13);
this.lbl_ADC_RPA0.TabIndex = 2;
this.lbl_ADC_RPA0.Text = "RPA0";
//
// lbl_UART_RPA0
//
this.lbl_UART_RPA0.AutoSize = true;
this.lbl_UART_RPA0.Location = new System.Drawing.Point(37, 65);
this.lbl_UART_RPA0.Name = "lbl_UART_RPA0";
this.lbl_UART_RPA0.Size = new System.Drawing.Size(35, 13);
this.lbl_UART_RPA0.TabIndex = 4;
this.lbl_UART_RPA0.Text = "RPA0";
//
// cb_UART_RPA0
//
this.cb_UART_RPA0.AutoSize = true;
this.cb_UART_RPA0.Location = new System.Drawing.Point(16, 65);
this.cb_UART_RPA0.Name = "cb_UART_RPA0";
this.cb_UART_RPA0.Size = new System.Drawing.Size(15, 14);
this.cb_UART_RPA0.TabIndex = 5;
this.cb_UART_RPA0.UseVisualStyleBackColor = true;
this.cb_UART_RPA0.CheckedChanged += new System.EventHandler(this.cb_UART_RPA0_CheckedChanged);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(758, 429);
this.Controls.Add(this.tabControl1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.tabControl1.ResumeLayout(false);
this.tpPWM.ResumeLayout(false);
this.tpPWM.PerformLayout();
this.tpUART.ResumeLayout(false);
this.tpUART.PerformLayout();
this.tpADC.ResumeLayout(false);
this.tpADC.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tpPWM;
private System.Windows.Forms.Label lbl_PWM_RPA0;
private System.Windows.Forms.CheckBox cb_PWM_RPA0;
private System.Windows.Forms.TabPage tpUART;
private System.Windows.Forms.TabPage tpADC;
private System.Windows.Forms.Label lbl_ADC_RPA0;
private System.Windows.Forms.CheckBox cb_ADC_RPA0;
private System.Windows.Forms.ToolTip toolTip1;
private System.Windows.Forms.CheckBox cb_UART_RPA0;
private System.Windows.Forms.Label lbl_UART_RPA0;
}
}
I created a new UserControl which only contains a button.
public partial class TooltipButton : UserControl
{
public TooltipButton()
{
InitializeComponent();
}
public new bool Enabled
{
get { return button.Enabled; }
set { button.Enabled = value; }
}
[Category("Appearance")]
[Description("The text displayed by the button.")]
[EditorBrowsable(EditorBrowsableState.Always)]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Bindable(true)]
public override string Text
{
get { return button.Text; }
set { button.Text = value; }
}
[Category("Action")]
[Description("Occurs when the button is clicked.")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public new event EventHandler Click;
private void button_Click(object sender, EventArgs e)
{
// Bubble event up to parent
Click?.Invoke(this, e);
}
}
I found Serge_Yubenko's code worked on disabled buttons , but in order to stop the flashing make sure the tooltip pops up away from the button - just don't position it half way down the control but do this:
mFormTips.Show(toolTipString, control, control.Width / 2, control.Height);
instead of
mFormTips.Show(toolTipString, control, control.Width / 2, control.Height / 2);
This seems to follow the usual tooltip placement too...
So, I came across this post in my efforts to do the same thing, being the top result on Google. I had already considered the mouse move event and while the answers here did help, they didn't provide me with exactly what I wanted - that being a perfect recreation of the original show tooltip event.
The problem I discovered was this: For whatever reason in the API, ToolTip.Show turns the Mouse Move Event into effectively a Mouse Hover Event. Which is why the tooltip keeps flashing.
The workaround as suggested was to keep the tooltip on always show, or to display the tooltip away from the control, but that wouldn't be a faithful recreation, from the show to the timed fade. The answer would suggest that a block to prevent further execution of the code is needed - the reality was 2 blocks in the event code (One of which has no earthly reason existing and yet without it a timed event fires twice ***), a double delclaration of the control location, one inside the event, one class wide, and another class wide to check if the mouse is over a control, a class wide timer, and a Mouse Leave event to clean up due to too fast mouse movement away from the panel housing the control.
As you will see there are two events on the timer, both functions for them are in the event code as they need to reference variables get/set in the code. They can be moved out, but would then need class wide declarations on the variables, and they cause no harm where they are. FYI: "ToolTips" in the code is referencing the ToolTip control I have on the form.
*** Just to expand. If you look at the code you'll see that IsTipReset could be replaced with IsShown - after all they end up at the same value as each other. The reason for IsTipRest is this: If IsShown is used then while moving the mouse inside the control while the tootip is showing will cause a slight hiccup when the tooltip fades and very very very briefly another tooltip will popup. Using IsTipReset stops that. I have no idea why and maybe someone will spot it because I sure can't! Lol.
This is my first post here, and I realise it is an old thread, but I just wanted to share the fruits of my labour. As I said, my goal was a faithful recreation of tooltip and I think I achieved it. Enjoy!
using Timer = System.Windows.Forms.Timer;
private readonly Timer MouseTimer = new();
private Control? Ctrl;
private bool IsControl = false;
private void TopMenuMouseMove (object sender, MouseEventArgs e) {
Panel Pnl = (Panel)sender;
Control Area = Pnl.GetChildAtPoint (e.Location);
bool IsShown = false;
bool IsTipReset = false;
if (Area != null && Area.Enabled == false && Area.Visible == true) {
Ctrl = Pnl.GetChildAtPoint (e.Location);
Point Position = e.Location;
if (IsControl) { IsShown = true; } else if (!IsControl) { IsControl = true; IsShown = false; }
if (!IsShown) {
MouseTimer.Interval = ToolTips.InitialDelay;
MouseTimer.Tick += new EventHandler (TimerToolTipShow!);
MouseTimer.Start ();
}
void TimerToolTipShow (object sender, EventArgs e) {
if (!IsTipReset) {
MouseTimer.Dispose ();
string Txt = ToolTips.GetToolTip (Ctrl) + " (Disabled)";
Position.Offset (-Ctrl.Left, 16);
ToolTips.Show (Txt, Ctrl, Position);
MouseTimer.Interval = ToolTips.AutoPopDelay;
MouseTimer.Tick += new EventHandler (TimerToolTipReset!);
MouseTimer.Start ();
IsShown = true;
IsTipReset = true;
}
}
void TimerToolTipReset (object sender, EventArgs e) {
if (IsShown) {
MouseTimer.Dispose ();
IsShown = false;
ToolTips.Hide (Ctrl);
}
}
}
else if (Area == null) {
if (Ctrl != null) {
MouseTimer.Dispose ();
IsShown = false;
IsControl = false;
ToolTips.Hide (Ctrl);
Ctrl = null;
}
}
}
private void TopMenuMouseLeave (object sender, EventArgs e) {
if (Ctrl != null) {
MouseTimer.Dispose ();
IsControl = false;
ToolTips.Hide (Ctrl);
Ctrl = null;
}
}

Categories