Why tooltip, displayed manually with ToolTip.Show, is not shown, when window, containing control, is inactive?
public class MyControl : Button
{
private _tip;
public string ToolTip
{
get { return _tip; }
set { _tip = value; }
}
private ToolTip _toolTip = new ToolTip();
public MyControl()
{
_toolTip.UseAnimation = false;
_toolTip.UseFading = false;
_toolTip.ShowAlways = true;
}
protected override void OnMouseHover(EventArgs e)
{
_toolTip.Show(_tip, this, 0, Height);
base.OnMouseHover(e);
}
protected override void OnMouseLeave(EventArgs e)
{
_toolTip.Hide(this);
base.OnMouseLeave(e);
}
}
I went for ToolTip.Show because I must have tooltip onscreen for unlimited time, which is not possible with normal ToolTip. I also love the idea of having tooltip text as a part of control itself. But unfortunately, when showing tooltip this way for inactive window (despite ShowAlways = true), it simply doesn't work.
The OnMouseHower event is rised, but _toolTip.Show does nothing.. unless window is activated, then everything works.
Bounty
Adding bounty for a solution to display tooltip for an inactive form (preferably with solution when tooltip text is a property of control, not IContainer).
There is a private method that does what you want, so to access it, you would have to use reflection to call it:
using System.Reflection;
public class MyControl : Button {
private ToolTip toolTip = new ToolTip() {
UseAnimation = false,
UseFading = false
};
public string ToolTip { get; set; }
protected override void OnMouseHover(EventArgs e) {
base.OnMouseHover(e);
Point mouse = MousePosition;
mouse.Offset(10, 10);
MethodInfo m = toolTip.GetType().GetMethod("SetTool",
BindingFlags.Instance | BindingFlags.NonPublic);
m.Invoke(toolTip, new object[] { this, this.ToolTip, 2, mouse });
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
toolTip.Hide(this);
}
}
The tip will display on an inactive window and it will stay on the screen indefinitely until the mouse moves off the control.
Related
I'm developing a Windows Desktop Application using C# in VS 2022 on Windows 10. I'm developing for a touch screen and want the UI to be very intuitive and give good feedback because the user(s) will likely be tech-averse. On several of the forms I'm using a PictureBox as a button because I like the visual effects better. I can get a nice "button pressed" effect by using the MouseDown and MouseUp events to change the border style of the PictureBox to Fixed3D (on mouse down) and back to None (on mouse up). The only issue is that the PictureBox image "blinks" when I do this, like the control is clearing the image out and reloading it or something.
My code is rather trivial, but I'll post it here anyway just in case:
private void Button_Down(object sender, MouseEventArgs e)
{
PictureBox pb = (PictureBox)sender;
pb.BorderStyle = BorderStyle.Fixed3D;
}
private void Button_Up(object sender, MouseEventArgs e)
{
PictureBox pb = (PictureBox)sender;
pb.BorderStyle = BorderStyle.None;
}
If you're open to solving your blinking problem another way, consider this reusable CustomButton class that lets you use your own custom image to depict the 3D pressed state. The icons are superimposed using the Text property and a custom font containing glyphs (making it easy to change their size and color). When the button is not pressed, system theme takes over or you could unset the UseVisualStyleBackColor property to additionally customize things like OnMouseHover.
CustomButton inherits from Buttonand has a PrivateFontCollection giving it access to a .ttf file containing glyphs. This particular flashlight-filter-history-favorite-search.ttf is one I designed for my own project using the Fontello open-source icon font generator.
public CustomButton()
{
UseCompatibleTextRendering = true;
TextAlign = ContentAlignment.MiddleCenter;
refCount++;
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (!DesignMode) initFont();
}
private void initFont()
{
if (privateFontCollection == null)
{
privateFontCollection = new PrivateFontCollection();
var path = Path.Combine(Path.GetDirectoryName(
Assembly.GetEntryAssembly().Location),
"Fonts",
"flashlight-filter-history-favorite-search.ttf");
privateFontCollection.AddFontFile(path);
var fontFamily = privateFontCollection.Families[0];
GlyphFontUp = new Font(fontFamily, 16F);
GlyphFontDown = new Font(fontFamily, 15F);
}
Font = GlyphFontUp;
ForeColor = GlyphColorUp;
}
PrivateFontCollection privateFontCollection = null;
public static Font GlyphFontUp { get; private set; } = null;
public static Font GlyphFontDown { get; private set; } = null;
public static Color GlyphColorUp { get; } = Color.Teal;
public static Color GlyphColorDown { get; } = Color.DarkCyan;
private static int refCount = 0;
protected override void Dispose(bool disposing)
{
if (disposing)
{
refCount--;
if (refCount == 0)
{
GlyphFontUp?.Dispose();
privateFontCollection?.Dispose();
}
}
base.Dispose(disposing);
}
When the mouse is down the button has an image, the icon is smaller, and the icon color changes. When the mouse comes up the image is removed and everything goes back to normal.
partial class CustomButton : Button
{
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
Image = new Bitmap(Resources.buttonDown, Size);
Font = GlyphFontDown;
ForeColor = GlyphColorDown;
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
Font = GlyphFontUp;
ForeColor = GlyphColorUp;
Image = null;
}
}
The buttonDown image is just something I drew myself. I'm pretty sure you could do better!
The CustomButton class can be swapped out in the MainForm.Designer.cs file.
// private System.Windows.Forms.Button customButton0;
private intuitive_buttons.CustomButton customButton0;
This code assigns the various icons to the buttons:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
// Assign the icons to the buttons
customButton0.Text = "\uE800";
customButton1.Text = "\uE801";
customButton2.Text = "\uE802";
customButton3.Text = "\uE803";
customButton4.Text = "\uE804";
}
}
Hope this at least gives you a few ideas to try.
I have created a custom NSView that i would like to place over the top of the content of a window to block any interaction while all the content is loading. The problem i was having is that i could click through the NSView to the controls below though that has now been fixed. The new problem is that even though i cannot click on the controls, when i move the mouse over text controls, the mouse switches to the I Beam icon.
How do i make the NSView completely block all interaction with everything below it?
The NSView i created is below:
[Register("StupidView")]
public class StupidView : NSView
{
public StupidView()
{
// Init
Initialize();
}
public StupidView(IntPtr handle) : base (handle)
{
// Init
Initialize();
}
[Export("initWithFrame:")]
public StupidView(CGRect frameRect) : base(frameRect) {
// Init
Initialize();
}
private void Initialize()
{
this.AcceptsTouchEvents = true;
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}
public override void DrawRect(CGRect dirtyRect)
{
var ctx = NSGraphicsContext.CurrentContext.GraphicsPort;
ctx.SetFillColor(new CGColor(128, 128, 128, 0.7f));
ctx.FillRect(dirtyRect);
}
public override void MouseDown(NSEvent theEvent)
{
if (Hidden)
{
base.MouseDown(theEvent);
}
}
public override void MouseDragged(NSEvent theEvent)
{
if (Hidden)
{
base.MouseDragged(theEvent);
}
}
public override bool AcceptsFirstResponder()
{
return !this.Hidden;
}
public override bool AcceptsFirstMouse(NSEvent theEvent)
{
return !this.Hidden;
}
public override NSView HitTest(CGPoint aPoint)
{
return Hidden ? null : this;
}
}
I had the same problem a few weeks ago, and here is how I could manage this :
First, to prevent user interactions on the superview placed below, I added a transparent button which was there only to catch the mouse click and, if you don't have to do anything, do nothing :
private void Initialize()
{
this.AcceptsTouchEvents = true;
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
//Add button to prevent user interactions
NSButton buttonToPreventUserInteraction = new NSButton();
buttonToPreventUserInteraction.Bordered = false;
buttonToPreventUserInteraction.Transparent = true;
buttonToPreventUserInteraction.TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview(buttonToPreventUserInteraction);
//If you want to add some constraints on the button, for it to resize and keep the same size of your subview
var dicoViews = new NSMutableDictionary();
dicoViews.Add((NSString)"buttonToPreventUserInteraction", buttonToPreventUserInteraction);
NSLayoutConstraint[] buttonToPreventUserInteractionHorizontalConstraints = NSLayoutConstraint.FromVisualFormat("H:|[buttonToPreventUserInteraction]|", NSLayoutFormatOptions.DirectionLeadingToTrailing, null, dicoViews);
NSLayoutConstraint[] buttonToPreventUserInteractionVerticalConstraints = NSLayoutConstraint.FromVisualFormat("V:|[buttonToPreventUserInteraction]|", NSLayoutFormatOptions.DirectionLeadingToTrailing, null, dicoViews);
AddConstraints(buttonToPreventUserInteractionHorizontalConstraints);
AddConstraints(buttonToPreventUserInteractionVerticalConstraints);
}
For your other problem, which is the mouse cursor changing from the content in your superview placed below, you can add a NSTrackingArea on your subview, and implement the override method "MouseMoved" to change the cursor. You can do something like this :
First Add the NSTrackingArea on your subview (you can put this code in your "Initialize" method)
NSTrackingAreaOptions opts = ((NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.InVisibleRect));
var trackingArea = new NSTrackingArea(new CGRect(0, 0, FittingSize.Width, FittingSize.Height), opts, Self, null);
AddTrackingArea(trackingArea);
And then implement the override method :
public override void MouseMoved(NSEvent theEvent)
{
//You can choose the type of cursor you want to use here
NSCursor.ArrowCursor.Set();
}
This made it for me, hope it will for you too
I am creating a custom control in my C# application in order to add a new property (MyProperty below). It is inheriting from Label. One thing I would like it to do, is display at a particular size when I drag it on to my form (200x132). I'd also like it to display no text. However, no matter how I try to do this, it doesn't seem to work. I am able to set BackColor and BorderStyle with no problem, however. I'm fairly new to C#, so maybe I'm missing something obvious.
Here is my code:
using System.Drawing;
using System.Windows.Forms;
namespace MyProgram
{
public enum MyEnum
{
Value1, Value2, Value3
}
public partial class MyControl : Label
{
public MyControl()
{
BackColor = Color.LightCoral;
BorderStyle = BorderStyle.FixedSingle;
AutoSize = false;
Size = new Size(200, 132);
Text = "";
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
private MyEnum myProperty;
public MyEnum MyProperty
{
get { return myProperty; }
set { myPropery = value; }
}
}
}
The answer provided via Dispersia's link has a bug, in my opinion. The text reset should happen once and then whatever a user does after that shouldn't matter. In Dispersia's link you can't actually set the text back to the control name because it will keep blanking it out.
The answer provided by cramopy doesn't technically answer your question, it is a way to do it by using the defaults on a UserControl though. You'll also need to bind the Text property of the UserControl to the label's.
The following should work while inheriting from a Label and will only reset the Text property once.
public partial class MyControl : Label
{
#region fields
private IComponentChangeService _changeService;
private bool canResetText = false;
#endregion
#region properties
protected override Size DefaultSize
{
get { return new Size(200, 132); }
}
[Browsable(false)]
public override bool AutoSize
{
get { return false; }
set { base.AutoSize = false; }
}
public override ISite Site
{
get { return base.Site; }
set
{
base.Site = value;
if (!base.DesignMode)
return;
this._changeService = (IComponentChangeService)base.GetService(typeof(IComponentChangeService));
if (this._changeService != null)
this._changeService.ComponentChanged += new ComponentChangedEventHandler(this.OnComponentChanged);
}
}
#endregion
#region constructors
public MyControl()
{
base.BackColor = Color.LightCoral;
base.BorderStyle = BorderStyle.FixedSingle;
}
#endregion
#region methods
protected override void InitLayout()
{
base.InitLayout();
this.canResetText = true;
}
private void OnComponentChanged(object sender, ComponentChangedEventArgs ce)
{
if (ce.Component != null &&
ce.Component == this &&
ce.Member.Name == "Text" &&
base.DesignMode &&
this.canResetText)
{
((MyControl)ce.Component).Text = string.Empty;
this.canResetText = false;
if (this._changeService != null)
this._changeService.ComponentChanged -= new ComponentChangedEventHandler(this.OnComponentChanged);
}
}
#endregion
}
#Dispersia reply only answers the myControl1 thing. (deleted meanwhile)
Here comes a full guide for solving your problem:
Add a new UserControl named MyLabel
Change the following within Designer Mode:
BorderStyle:= FixedSingle
Size:= 200; 132
Now Drag&Drop a new Label onto the control
Edit those Label values (also within Designer Mode):
AutoSize:= false
BackColor:= LightCoral
Dock:= Fill
Text:= clear/empty this box!! (don't write this inside the box, you really have to clear it!)
TextAlign:= MiddleCenter
Just recompile your project && add a MyLabel control from the Toolbar.
Now it show up as you wanted!!
What I am attempting to do is create a complex control that has a picture box, track slider and numeric up down controls. In the parent form, when the user clicks on an image, then this composite control appears and the background color is then sent to it and the image in the control is then set with that background color. Then if the user clicks on the image on the composite control, the parent form is then notified of the click event and then subsequently removes that specific composite control from the parent form.
Composite Control code
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace ctlClusterControlLib
{
public partial class UserControl1 : UserControl
{
private Color colImageBackground;
private int intThreadCount;
private PictureBox pictureBoxControl; // Compiler informs me that this is never assigned to and will always have its default value null.
private TrackBar trackBar; // Compiler informs me that this is never assigned to and will always have its default value null.
private NumericUpDown numericUpDown; // Compiler informs me that this is never assigned to and will always have its default value null.
private string strImageToolTip1;
private string strImageToolTip2;
private static object EventSubmitKey = new object();
public UserControl1()
{
InitializeComponent();
}
public Color ImageBackground
{
get { return colImageBackground; }
set { colImageBackground = value; Invalidate(); }
}
public int ThreadCount
{
get { return intThreadCount; }
set { intThreadCount = value; }
}
[
Category("Action"),
Description("Raised when the user clicks on the image.")
]
public event EventHandler PictureClick
{
add { Events.AddHandler(EventSubmitKey, value); }
remove { Events.RemoveHandler(EventSubmitKey, value); }
}
public event EventHandler TrackBarScroll
{
add { trackBar.Scroll += value; }
remove { trackBar.Scroll -= value; }
}
public event EventHandler numericUpDownChange
{
add { numericUpDown.ValueChanged += value; }
remove { numericUpDown.ValueChanged -= value; }
}
public string ImageToolTip1
{
get { return strImageToolTip1; }
set { strImageToolTip1 = value; }
}
public string ImageToolTip2
{
get { return strImageToolTip2; }
set { strImageToolTip2 = value; }
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
numericUpDown1.Value = trackBar1.Value;
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
trackBar1.Value = Convert.ToInt32(numericUpDown1.Value);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Color c = Color.FromArgb(0xFF, colImageBackground);
pictureBox1.BackColor = c;
}
}
}
Parent Form CS relevant section:
private void newPictureBox_Click(object sender, EventArgs e)
{
UserControl1 _UserControl = new UserControl1();
PictureBox _PictureBox = (PictureBox)sender;
string _NewControlClusterName = "_New" + _PictureBox.Name;
_UserControl.Name = _NewControlClusterName;
_UserControl.ThreadCount = 16;
_UserControl.ImageBackground = _PictureBox.BackColor;
_UserControl.Dock = DockStyle.Top;
_UserControl.PictureClick += new EventHandler(ClusterControl_Click);
//_UserControl.TrackBarScroll += new EventHandler(GetTartanCode);
panel3.Controls.Add(_UserControl);
panel3.Controls.SetChildIndex(_UserControl, 0);
}
And I am having intermittent issues with raising the click event to the parent form using this control.
I have tried everything I can find in Google and Stack Overflow with no joy. My questions are this:
Am I even in the right ballpark?
Is this something that needs to be coded in the parent form cs file?
Is this something that needs to be reconfigured in the composite control cs file?
Is this something that needs to be configured in both files?
I believe I have a solution.
What I was not doing was directly assigning the request to the control I wanted to register the event for. Instead I was assigning it to a new control and therefore nothing would happen.
public event EventHandler PictureClick
{
add { pictureBox1.Click += value; }
remove { pictureBox1.Click -= value; }
}
And so far, It works every time.
I needed functionality that doesn't exist in the standard ComboBox, so I wrote my own from a TextBox and a form. When the user types in the TextBox, it shows a dropdown as a separate form.
Here's some of the relevant code:
internal class FilteredDropDown : Form
{
public Control OwnerControl { get; set; }
public bool CloseOnLostFocus { get; set; }
protected override OnLostFocus(EventArgs e)
{
if (CloseOnLostFocus && !OwnerControl.IsFocused)
this.Close();
}
protected override OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e)
// highlight the moused over item in the list
}
...
}
public class FilteredCombo : TextBox
{
private FilteredDropDown dropDown;
public FilteredCombo()
{
dropDown = new FilteredDropDown();
dropDown.OwnerControl = this;
}
public void ShowDropDown()
{
if (dropDown.Visible)
return;
dropDown.RefreshFilter();
var loc = PointToScreen(new Point(0, this.Height));
dropDown.Location = loc;
dropDown.CloseOnLostFocus = false;
int selectionStart = this.SelectionStart;
int selectionLength = this.SelectionLength;
dropDown.Show(this);
this.Focus();
this.SelectionStart = selectionStart;
this.SelectionLength = selectionLength;
dropDown.CloseOnLostFocus = false;
}
protected override OnLostFocus(EventArgs e)
{
if (dropDown.Visible && !dropDown.ContainsFocus())
dropDown.Close();
}
protected override OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
ShowDropDown();
}
...
}
There's obviously a whole lot more code than that to deal with all kinds of stuff irrelevent to my question.
The problem is when I put the FilteredCombo on a modal dialog. Somehow the FilteredDropDown form doesn't receive mouse events at all when it is parented by a modal dialog.
I've read something about WinForms filtering out events on all except the current modal dialog, I suspect that is what's going on, but I have no ideas of how to fix it. Is there some way to get the mouse up/down/move/click/etc. events to work when parented by a model dialog?
I had to go digging through the ShowDialog source code, and I found that it calls user32.dll EnableWindow(Handle, false) on all the windows except the shown one. The problem was that the FilteredDropDown already existed by the time the ShowDialog() method got called. I discovered two different ways to fix this:
Don't allow the DropDown to be shown until the parent form is shown. This is a bit trickier to guarantee, so I also implemented the second way.
Re-enable the DropDown window when it is made visible:
[DllImport("user32.dll")]
private static extern bool EnableWindow(IntPtr hWnd, bool enable);
protected override void OnVisibleChanged(EventArg e)
{
base.OnVisibleChanged(e);
if (this.Visible)
{
EnableWindow(this.Handle, true);
}
}