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.
Related
The original code comes from this answer:
How to animate dots in UserControl Paint event?
private void DotsTimer_Tick(object sender, EventArgs e)
{
currentDot += 1;
currentDot %= m_NumberOfDots;
dotsTimer.Interval = TimerInterval;
Invalidate();
}
I want that the interval property will show when I'm dragging the control in form1 designer like the m_DotColor for example.
This line creates the problem in the DotsTimer_Tick event:
dotsTimer.Interval = TimerInterval;
but when I'm dragging the control now into the Form's Designer, the whole project freeze shut down and Visual Studio start over again and loading the project again.
A screenshot of the PropertyGrid, without the interval part in the tick event.
I removed the line from the Tick event. In the properties, the dot color and dot active color are listed in the properties; I want to change the Interval value in the same way.
Screenshot of the control on form1 designer:
Now I can change the colors of the DotActiveColor and DotColor before running the program! The same I want to do with the Interval to be able to change the speed of the timer before running the program.
If you want to see in the designer what the animation is going to be, you can add a public Property that allows to start / stop the Timer at Design-Time.
Note that you have to initialize the backing Field of a Property to the value set as DefaultValue, as in here:
private int m_Interval = 200;
The DefaultValue attribute doesn't set the Field, it prevents the serialization of the Property value if it matches the value set as the default.
I've added a AnimationEnabled public Property that can be set in the PropertyGrid, to start and stop the animation on demand.
Do not start the Timer in the Constructor of your UserControl. If you want to see the animation when the UserControl is first created (when dropped on a Form), you may use the OnHandleCreated() override. I.e., don't start the Timer until your UC has a Handle.
Also, the System.Windows.Forms.Timer has an official maximum resolution (min. Interval) of 55ms, though it can work at 35ms. At 55ms it's already a quite fast animation anyway.
public partial class LoadingLabel : UserControl
{
// [...]
private Timer dotsTimer = null;
private int m_Interval = 200;
// [...]
public LoadingLabel() {
InitializeComponent();
components = new Container();
dotsTimer = new Timer(components) { Interval = m_Interval };
dotsTimer.Tick += DotsTimer_Tick;
DoubleBuffered = true;
Padding = new Padding(5);
}
[DefaultValue(false)]
public bool AnimationEnabled {
get => dotsTimer.Enabled;
set {
if (value) Start(); else Stop();
}
}
[DefaultValue(200)]
public int TimerInterval {
get => m_Interval;
set {
value = Math.Max(55, Math.Min(value, 500));
if (m_Interval != value) {
m_Interval = value;
dotsTimer.Interval = m_Interval;
}
}
}
[DefaultValue(5)]
public int NumberOfDots {
get => m_NumberOfDots;
set {
value = Math.Max(3, Math.Min(value, 7));
if (m_NumberOfDots != value) {
m_NumberOfDots = value;
bool running = dotsTimer.Enabled;
Stop();
SetMinSize();
if (running) Start();
}
}
}
[DefaultValue(typeof(Color), "Cyan")]
public Color DotColor {
get => m_DotColor;
set {
m_DotColor = value;
Invalidate();
}
}
[DefaultValue(typeof(Color), "Blue")]
public Color DotActiveColor {
get => m_DotActiveColor;
set {
m_DotActiveColor = value;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
for (int dot = 0; dot < m_NumberOfDots; dot++) {
var color = dot == currentDot ? DotActiveColor : DotColor;
var pos = Padding.Left + (dotSize + dotSpacing) * dot;
using (var brush = new SolidBrush(color)) {
e.Graphics.FillEllipse(brush, pos, Padding.Top, dotSize, dotSize);
}
}
base.OnPaint(e);
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
SetMinSize();
// Start the Timer here - eventually - and change the default value of
// AnimationEnabled to true
// Start();
}
protected override void OnHandleDestroyed(EventArgs e) {
Stop();
base.OnHandleDestroyed(e);
}
private void DotsTimer_Tick(object sender, EventArgs e) {
currentDot += 1;
currentDot %= m_NumberOfDots;
Invalidate();
}
public void Start() => dotsTimer.Start();
public void Stop() {
dotsTimer.Stop();
currentDot = 0;
Invalidate();
}
private void SetMinSize() {
var width = Padding.Left + Padding.Right +
(dotSize * m_NumberOfDots) + (dotSpacing * (m_NumberOfDots - 1)) + 1;
var height = Padding.Top + Padding.Bottom + dotSize + 1;
MinimumSize = new Size((int)width, (int)height);
Size = MinimumSize;
}
}
This is how it looks now at Design-Time:
Starting / stopping the Timer and changing the Interval
I am trying to make a rss news ticker which will display the text,the text need move from left to right
I made the the code and text is moving from left to right but after a specific time its not showing the full text,i will be adding more news from the admin pannel,each time i add the news the text is not showing after the first scroll
the below screenshot is after a specific amount of time,only a part of news is displaying
Code used
int x = -800,y=1;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
private void timer1_Tick(object sender, System.EventArgs e)
{
label1.SetBounds(x, y, 1, 1);
x++;
if(x>=800)
{
x = 4;
}
}
Code for reading xml
private void StartRssThread()
{
List<RssChannel> channels = new List<RssChannel>();
StringBuilder mergedFeed = new StringBuilder();
int mh = 0;
int ms = 0;
if (mh < 7)
{
RssFeed DaFeed = RssFeed.Read("http://shjc.ae/rss/fileName.xml");
RssChannel DaChannel = (RssChannel)DaFeed.Channels[0];
channels.Add(DaChannel);
mergedFeed.AppendFormat(" {0}: ", DaChannel.Title);
foreach (RssItem sTrm in DaChannel.Items)
{
if (ms < 10)
{
mergedFeed.AppendFormat(" {0} |", sTrm.Title);
ms++;
mh++;
}
}
}
string dafeed = mergedFeed.ToString();
mergedFeed = null;
textBox1.Invoke(new UpdateUiCallback(this.UpdateUi), new string[] { dafeed });
}
Windows Forms Marquee Label - Horizontal
I've already posted an example of how to create a Marquee Label to animate text from top to bottom, by using a Timer and overriding OnPaint method of a custom draw control in this post: Windows Forms Top to Bottom Marquee Label.
In the following code, I've changed that example to animates the text horizontally from right to left or left to right. It's enough to set RightToLeft property of the control to change the direction. Also don't forget to set AutoSize to false.
Example - Right to Left and Left to Right Marquee Label in Windows Forms
using System;
using System.Drawing;
using System.Windows.Forms;
public class MarqueeLabel : Label
{
Timer timer;
public MarqueeLabel()
{
DoubleBuffered = true;
timer = new Timer() { Interval = 100 };
timer.Enabled = true;
timer.Tick += Timer_Tick;
}
int? left;
int textWidth = 0;
private void Timer_Tick(object sender, EventArgs e)
{
if (RightToLeft == RightToLeft.Yes)
{
left += 3;
if (left > Width)
left = -textWidth;
}
else
{
left -= 3;
if (left < -textWidth)
left = Width;
}
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(BackColor);
var s = TextRenderer.MeasureText(Text, Font, new Size(0, 0),
TextFormatFlags.TextBoxControl | TextFormatFlags.SingleLine);
textWidth = s.Width;
if (!left.HasValue) left = Width;
var format = TextFormatFlags.TextBoxControl | TextFormatFlags.SingleLine |
TextFormatFlags.VerticalCenter;
if (RightToLeft == RightToLeft.Yes)
{
format |= TextFormatFlags.RightToLeft;
if (!left.HasValue) left = -textWidth;
}
TextRenderer.DrawText(e.Graphics, Text, Font,
new Rectangle(left.Value, 0, textWidth, Height),
ForeColor, BackColor, format);
}
protected override void Dispose(bool disposing)
{
if (disposing)
timer.Dispose();
base.Dispose(disposing);
}
}
You are using hard coded values for the start and max value of x. From your question I think the text in the label has a dynamic length (correct?). If the text is of dynamic length the value of x should also be dynamic.
Also, x starts at -800. Then slowly grows to 800 and then it gets set to 4.
This seems strange to me, if the first run started at -800, the second run may also need to start at -800.
Hope this somewhat helped you. If not, please provide more details (like why you chose -800, 800 and 4).
I have a control (System.Windows.Forms.ScrollableControl) which can potentially be very large. It has custom OnPaint logic. For that reason, I am using the workaround described here.
public class CustomControl : ScrollableControl
{
public CustomControl()
{
this.AutoScrollMinSize = new Size(100000, 500);
this.DoubleBuffered = true;
}
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
graphics.Clear(this.BackColor);
...
}
}
The painting code mainly draws "normal" things that move when you scroll. The origin of each shape that is drawn is offsetted by this.AutoScrollPosition.
graphics.DrawRectangle(pen, 100 + this.AutoScrollPosition.X, ...);
However, the control also contains "static" elements, which are always drawn at the same position relative to the parent control. For that, I just don't use AutoScrollPosition and draw the shapes directly:
graphics.DrawRectangle(pen, 100, ...);
When the user scrolls, Windows translates the entire visible area in the direction opposite to the scrolling. Usually this makes sense, because then the scrolling seems smooth and responsive (and only the new part has to be redrawn), however the static parts are also affected by this translation (hence the this.Invalidate() in OnScroll). Until the next OnPaint call has successfully redrawn the surface, the static parts are slightly off. This causes a very noticable "shaking" effect when scrolling.
Is there a way I can create a scrollable custom control that does not have this problem with static parts?
You could do this by taking full control of scrolling. At the moment, you're just hooking in to the event to do your logic. I've faced issues with scrolling before, and the only way I've ever managed to get everything to work smoothly is by actually handling the Windows messages by overriding WndProc. For instance, I have this code to synchronize scrolling between several ListBoxes:
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
// 0x115 and 0x20a both tell the control to scroll. If either one comes
// through, you can handle the scrolling before any repaints take place
if (m.Msg == 0x115 || m.Msg == 0x20a)
{
//Do you scroll processing
}
}
Using WndProc will get you the scroll messages before anything gets repainted at all, so you can appropriately handle the static objects. I'd use this to suspend scrolling until an OnPaint occurs. It won't look as smooth, but you won't have issues with the static objects moving.
Since I really needed this, I ended up writing a Control specifically for the case when you have static graphics on a scrollable surface (whose size can be greater than 65535).
It is a regular Control with two ScrollBar controls on it, and a user-assignable Control as its Content. When the user scrolls, the container sets its Content's AutoScrollOffset accordingly. Therefore, it is possible to use controls which use the AutoScrollOffset method for drawing without changing anything. The Content's actual size is exactly the visible part of it at all times. It allows horizontal scrolling by holding down the shift key.
Usage:
var container = new ManuallyScrollableContainer();
var content = new ExampleContent();
container.Content = content;
container.TotalContentWidth = 150000;
container.TotalContentHeight = 5000;
container.Dock = DockStyle.Fill;
this.Controls.Add(container); // e.g. add to Form
Code:
It became a bit lengthy, but I could avoid ugly hacks. Should work with mono. I think it turned out pretty sane.
public class ManuallyScrollableContainer : Control
{
public ManuallyScrollableContainer()
{
InitializeControls();
}
private class UpdatingHScrollBar : HScrollBar
{
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
// setting the scroll position programmatically shall raise Scroll
this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
}
}
private class UpdatingVScrollBar : VScrollBar
{
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
// setting the scroll position programmatically shall raise Scroll
this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
}
}
private ScrollBar shScrollBar;
private ScrollBar svScrollBar;
public ScrollBar HScrollBar
{
get { return this.shScrollBar; }
}
public ScrollBar VScrollBar
{
get { return this.svScrollBar; }
}
private void InitializeControls()
{
this.Width = 300;
this.Height = 300;
this.shScrollBar = new UpdatingHScrollBar();
this.shScrollBar.Top = this.Height - this.shScrollBar.Height;
this.shScrollBar.Left = 0;
this.shScrollBar.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
this.svScrollBar = new UpdatingVScrollBar();
this.svScrollBar.Top = 0;
this.svScrollBar.Left = this.Width - this.svScrollBar.Width;
this.svScrollBar.Anchor = AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
this.shScrollBar.Width = this.Width - this.svScrollBar.Width;
this.svScrollBar.Height = this.Height - this.shScrollBar.Height;
this.Controls.Add(this.shScrollBar);
this.Controls.Add(this.svScrollBar);
this.shScrollBar.Scroll += this.HandleScrollBarScroll;
this.svScrollBar.Scroll += this.HandleScrollBarScroll;
}
private Control _content;
/// <summary>
/// Specifies the control that should be displayed in this container.
/// </summary>
public Control Content
{
get { return this._content; }
set
{
if (_content != value)
{
RemoveContent();
this._content = value;
AddContent();
}
}
}
private void AddContent()
{
if (this.Content != null)
{
this.Content.Left = 0;
this.Content.Top = 0;
this.Content.Width = this.Width - this.svScrollBar.Width;
this.Content.Height = this.Height - this.shScrollBar.Height;
this.Content.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
this.Controls.Add(this.Content);
CalculateMinMax();
}
}
private void RemoveContent()
{
if (this.Content != null)
{
this.Controls.Remove(this.Content);
}
}
protected override void OnParentChanged(EventArgs e)
{
// mouse wheel events only arrive at the parent control
if (this.Parent != null)
{
this.Parent.MouseWheel -= this.HandleMouseWheel;
}
base.OnParentChanged(e);
if (this.Parent != null)
{
this.Parent.MouseWheel += this.HandleMouseWheel;
}
}
private void HandleMouseWheel(object sender, MouseEventArgs e)
{
this.HandleMouseWheel(e);
}
/// <summary>
/// Specifies how the control reacts to mouse wheel events.
/// Can be overridden to adjust the scroll speed with the mouse wheel.
/// </summary>
protected virtual void HandleMouseWheel(MouseEventArgs e)
{
// The scroll difference is calculated so that with the default system setting
// of 3 lines per scroll incremenet,
// one scroll will offset the scroll bar value by LargeChange / 4
// i.e. a quarter of the thumb size
ScrollBar scrollBar;
if ((Control.ModifierKeys & Keys.Shift) != 0)
{
scrollBar = this.HScrollBar;
}
else
{
scrollBar = this.VScrollBar;
}
var minimum = 0;
var maximum = scrollBar.Maximum - scrollBar.LargeChange;
if (maximum <= 0)
{
// happens when the entire area is visible
return;
}
var value = scrollBar.Value - (int)(e.Delta * scrollBar.LargeChange / (120.0 * 12.0 / SystemInformation.MouseWheelScrollLines));
scrollBar.Value = Math.Min(Math.Max(value, minimum), maximum);
}
public event ScrollEventHandler Scroll;
protected virtual void OnScroll(ScrollEventArgs e)
{
var handler = this.Scroll;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Event handler for the Scroll event of either scroll bar.
/// </summary>
private void HandleScrollBarScroll(object sender, ScrollEventArgs e)
{
OnScroll(e);
if (this.Content != null)
{
this.Content.AutoScrollOffset = new System.Drawing.Point(-this.HScrollBar.Value, -this.VScrollBar.Value);
this.Content.Invalidate();
}
}
private int _totalContentWidth;
public int TotalContentWidth
{
get { return _totalContentWidth; }
set
{
if (_totalContentWidth != value)
{
_totalContentWidth = value;
CalculateMinMax();
}
}
}
private int _totalContentHeight;
public int TotalContentHeight
{
get { return _totalContentHeight; }
set
{
if (_totalContentHeight != value)
{
_totalContentHeight = value;
CalculateMinMax();
}
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
CalculateMinMax();
}
private void CalculateMinMax()
{
if (this.Content != null)
{
// Reduced formula according to
// http://msdn.microsoft.com/en-us/library/system.windows.forms.scrollbar.maximum.aspx
// Note: The original formula is bogus.
// According to the article, LargeChange has to be known in order to calculate Maximum,
// however, that is not always possible because LargeChange cannot exceed Maximum.
// If (LargeChange) == (1 * visible part of control), the formula can be reduced to:
if (this.TotalContentWidth > this.Content.Width)
{
this.shScrollBar.Enabled = true;
this.shScrollBar.Maximum = this.TotalContentWidth;
}
else
{
this.shScrollBar.Enabled = false;
}
if (this.TotalContentHeight > this.Content.Height)
{
this.svScrollBar.Enabled = true;
this.svScrollBar.Maximum = this.TotalContentHeight;
}
else
{
this.svScrollBar.Enabled = false;
}
// this must be set after the maximum is determined
this.shScrollBar.LargeChange = this.shScrollBar.Width;
this.shScrollBar.SmallChange = this.shScrollBar.LargeChange / 10;
this.svScrollBar.LargeChange = this.svScrollBar.Height;
this.svScrollBar.SmallChange = this.svScrollBar.LargeChange / 10;
}
}
}
Example content:
public class ExampleContent : Control
{
public ExampleContent()
{
this.DoubleBuffered = true;
}
static Random random = new Random();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
// random color to make the clip rectangle visible in an unobtrusive way
var color = Color.FromArgb(random.Next(160, 180), random.Next(160, 180), random.Next(160, 180));
graphics.Clear(color);
Debug.WriteLine(this.AutoScrollOffset.X.ToString() + ", " + this.AutoScrollOffset.Y.ToString());
CheckerboardRenderer.DrawCheckerboard(
graphics,
this.AutoScrollOffset,
e.ClipRectangle,
new Size(50, 50)
);
StaticBoxRenderer.DrawBoxes(graphics, new Point(0, this.AutoScrollOffset.Y), 100, 30);
}
}
public static class CheckerboardRenderer
{
public static void DrawCheckerboard(Graphics g, Point origin, Rectangle bounds, Size squareSize)
{
var numSquaresH = (bounds.Width + squareSize.Width - 1) / squareSize.Width + 1;
var numSquaresV = (bounds.Height + squareSize.Height - 1) / squareSize.Height + 1;
var startBoxH = (bounds.X - origin.X) / squareSize.Width;
var startBoxV = (bounds.Y - origin.Y) / squareSize.Height;
for (int i = startBoxH; i < startBoxH + numSquaresH; i++)
{
for (int j = startBoxV; j < startBoxV + numSquaresV; j++)
{
if ((i + j) % 2 == 0)
{
Random random = new Random(i * j);
var color = Color.FromArgb(random.Next(70, 95), random.Next(70, 95), random.Next(70, 95));
var brush = new SolidBrush(color);
g.FillRectangle(brush, i * squareSize.Width + origin.X, j * squareSize.Height + origin.Y, squareSize.Width, squareSize.Height);
brush.Dispose();
}
}
}
}
}
public static class StaticBoxRenderer
{
public static void DrawBoxes(Graphics g, Point origin, int boxWidth, int boxHeight)
{
int height = origin.Y;
int left = origin.X;
for (int i = 0; i < 25; i++)
{
Rectangle r = new Rectangle(left, height, boxWidth, boxHeight);
g.FillRectangle(Brushes.White, r);
g.DrawRectangle(Pens.Black, r);
height += boxHeight;
}
}
}
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);
}
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;
}
}