BlueMystical / Dark-Mode-Forms

Apply Dark Mode to all Controls in a Form [WinForms]
GNU General Public License v3.0
111 stars 15 forks source link

Accelerator key label text is wrong if target control is disabled #50

Closed TheIronWolfModding closed 1 month ago

TheIronWolfModding commented 2 months ago

Hi, It's me again :D Small bug (Note "&Focus ID" label):

image

Once target control is enabled, all is good:

image

BlueMystical commented 2 months ago

you mean the & symbol on it? thats very weird..

TheIronWolfModding commented 2 months ago

Yes, well, I can check Win11 for you when I get a chance. But this is standard way of access keys in Win32/WinForms - I press alt+f and focus goes to text box next to the label (next TabStop value).

BlueMystical commented 2 months ago

and it shows the & when it gets the focus only?

TheIronWolfModding commented 2 months ago

so the issue above happens when target text box is disabled. Once target text box is enabled, & becomes underscore F. But normally (without DM), it is always underscore F, independent of text box enablement state.

TheIronWolfModding commented 2 months ago

It does repro in Win11.

Unfortunately, in order to "enable" textbox, the game needs to be running. But note how without DM label looks correctly:

https://1drv.ms/u/s!ApNCoFDyNnCJhPVzSedVbqpx8T4AJw?e=8c0uQ0

kachnitata commented 1 month ago

hi, I came across this conversation. I think the reason is obvious in the code below:

...e.Graphics.DrawString(radio.Text ...

            if (control is Label)
            {
                control.GetType().GetProperty("BackColor")?.SetValue(control, control.Parent.BackColor);
                control.GetType().GetProperty("BorderStyle")?.SetValue(control, BorderStyle.None);
                control.Paint += (object sender, PaintEventArgs e) =>
                {
                    if (control.Enabled == false && this.IsDarkMode)
                    {
                        var radio = (sender as Label);
                        Brush B = new SolidBrush(control.ForeColor);

                        e.Graphics.DrawString(radio.Text, radio.Font,
                          B, new System.Drawing.PointF(1, 0));
                    }
                };
            }
kachnitata commented 1 month ago

I looked into the refactored code of Label.OnPaint() and found this:

e.Graphics.DrawString(this.Text, this.Font, brush, (RectangleF) rectangle, stringFormat);

and

internal virtual StringFormat CreateStringFormat() => ControlPaint.CreateStringFormat((Control) this, this.TextAlign, this.AutoEllipsis, this.UseMnemonic);

The key thing is the UseMnemonic:

Gets or sets a value indicating whether the control interprets an ampersand character (&) in the control's [Text] property to be an access key prefix character.

see MS documentation

kachnitata commented 1 month ago

actually, depending on the settings they use also TextRenderer.DrawText instead of Graphics.DrawString etc. I have no idea what details we will have to go into within DarkModeCS.

The whole method looks like this:

    protected override void OnPaint(PaintEventArgs e)
    {
      this.Animate();
      ImageAnimator.UpdateFrames(this.Image);
      Rectangle rectangle = LayoutUtils.DeflateRect(this.ClientRectangle, this.Padding);
      Image image = this.Image;
      if (image != null)
        this.DrawImage(e.Graphics, image, rectangle, this.RtlTranslateAlignment(this.ImageAlign));
      IntPtr hdc = e.Graphics.GetHdc();
      Color nearestColor;
      try
      {
        using (WindowsGraphics windowsGraphics = WindowsGraphics.FromHdc(hdc))
          nearestColor = windowsGraphics.GetNearestColor(this.Enabled ? this.ForeColor : this.DisabledColor);
      }
      finally
      {
        e.Graphics.ReleaseHdc();
      }
      if (this.AutoEllipsis)
      {
        Rectangle clientRectangle = this.ClientRectangle;
        Size preferredSize = this.GetPreferredSize(new Size(clientRectangle.Width, clientRectangle.Height));
        this.showToolTip = clientRectangle.Width < preferredSize.Width || clientRectangle.Height < preferredSize.Height;
      }
      else
        this.showToolTip = false;
      if (this.UseCompatibleTextRendering)
      {
        using (StringFormat stringFormat = this.CreateStringFormat())
        {
          if (this.Enabled)
          {
            using (Brush brush = (Brush) new SolidBrush(nearestColor))
              e.Graphics.DrawString(this.Text, this.Font, brush, (RectangleF) rectangle, stringFormat);
          }
          else
            ControlPaint.DrawStringDisabled(e.Graphics, this.Text, this.Font, nearestColor, (RectangleF) rectangle, stringFormat);
        }
      }
      else
      {
        TextFormatFlags textFormatFlags = this.CreateTextFormatFlags();
        if (this.Enabled)
        {
          TextRenderer.DrawText((IDeviceContext) e.Graphics, this.Text, this.Font, rectangle, nearestColor, textFormatFlags);
        }
        else
        {
          Color foreColor = TextRenderer.DisabledTextColor(this.BackColor);
          TextRenderer.DrawText((IDeviceContext) e.Graphics, this.Text, this.Font, rectangle, foreColor, textFormatFlags);
        }
      }
      base.OnPaint(e);
    }
BlueMystical commented 1 month ago

Ufff that look deep! Thanks for taking the time to investigate this.

BlueMystical commented 1 month ago

i think i fixed this issue:

image

The assigned 'Mnemonic' will show up as soon as you hit the ALT key, and will stay afterwards ! Going to Commit in a few mins and if @kachnitata and you (@TheIronWolfModding ) can test it and report back it would be great. Thanks folks!

TheIronWolfModding commented 1 month ago

Awesome, I'll try later this week and report back.

TheIronWolfModding commented 1 month ago

Hmmm, I tried but my app does not start I get parameter is wrong exception:

    System.Drawing.dll!System.Drawing.Graphics.GetHdc() Unknown
    System.Drawing.dll!System.Drawing.BufferedGraphics.RenderInternal(System.Runtime.InteropServices.HandleRef refTargetDC, System.Drawing.BufferedGraphics buffer) Unknown
    System.Drawing.dll!System.Drawing.BufferedGraphics.Render() Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.WmPaint(ref System.Windows.Forms.Message m)   Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m)   Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Label.WndProc(ref System.Windows.Forms.Message m) Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m)   Unknown
    System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam)  Unknown
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData)  Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context)    Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form mainForm)   Unknown
>   CCGEPMonitor.exe!CCGEPMonitor.Program.Main() Line 35    C#

I see that there's DarkModeBaseForm new class - does this have to be used as base instead of Form?

kachnitata commented 1 month ago

I am having the same issue. I think you @BlueMystical made two mistakes:

using (Graphics g = e.Graphics) would cause Disopose() of e.Graphics which probably leads to the errror

what I meant in my former post was to add style to the DrawString Call (instead of setting UseMnemonic)

I am going to fix it and post my solution

kachnitata commented 1 month ago

also I am not sure if we can

                        e.Graphics.Clear(control.Parent.BackColor);
                        e.Graphics.SmoothingMode = SmoothingMode.HighQuality;

maybe we should create new Grephics instance for it....

BlueMystical commented 1 month ago

The PR seems to work, thanks!

TheIronWolfModding commented 1 month ago

I would like to confirm that accelerate issue is fixed, thank you!

However, when I try to update my other project to the latest DMF files, I get regression:

Regressed look: image

Previous version looked like this: image

Has usage changed in some way, zero code changes on my side?

BlueMystical commented 1 month ago

@TheIronWolfModding : yes, a little bit:

TheIronWolfModding commented 1 month ago

Thank you - same problem. I think I have to stay on an old version then.

BlueMystical commented 1 month ago

try calling the ApplyTheme() method on the Form's Shown event.

TheIronWolfModding commented 1 month ago

try calling the ApplyTheme() method on the Form's Shown event.

Still does not work. Once removed minimize to tray startup it got a bit better, but for example progress bars are randomly not colorized. I suspect this is Win10 vs Win11 again.

kachnitata commented 1 month ago

Hi, I haven't noticed any issues myself.

btw @BlueMystical I think we really have to deal with the line e.Graphics.SmoothingMode = SmoothingMode.HighQuality; either get rid of it, or create a new instance somehow like: using (g=new Graphics()) {....} (I haven't tried it yet and I am out of office right now)

as the e.Graphics might be shared by multiple controls, it would unpredictably affect their smoothing mode. who knows, perhaps this leads to the issues reported by @TheIronWolfModding (but I was not able to reproduce in my app). I think that was your original point with the using block, you just put there e.Graphics by mistake...and I did not realize all this when I posted my fix...

BlueMystical commented 1 month ago

yeah that can be done, i used the SmoothingMode because i tough it would look a bit better, but it doesnt make much difference.