none
DateTimePickerの文字色を変更したい RRS feed

  • 質問

  • Visual C# 2019
    .NET Framework 4.7.2

    DateTimePickerで、文字色を変更したいと思ってコードしています。
    色々調べたところ、下記の手順で行えばできることが分かり、実装しています。
    Application.EnableVisualStyles();の記述は削除しない前提です。
     1.コントロールの描画内容をBitmapにコピーする。
     2.ImageAttributes.SetRemapTable() を利用して、文字色を入れ替えたものをGraphicsに描画する。
       (DateTimePickerの仕様上、元の文字色は確実にSystemColors.WindowTextである)
     3.Bitmapの内容をコントロールへ描画し直す。

    using System;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace Test
    {
        class DateTimePickerEx : DateTimePicker
        {
            private const int WM_PAINT = 0x000F;
    
            [DllImport("user32.dll")]
            public static extern IntPtr BeginPaint(HandleRef hWnd, ref PAINTSTRUCT lpPaint);
    
            [DllImport("user32.dll")]
            public static extern bool EndPaint(HandleRef hWnd, ref PAINTSTRUCT lpPaint);
    
            /// <summary>
            /// 背景色の変更を行います。
            /// </summary>
            /// <param name="m"></param>
            protected override void WndProc(ref Message m)
            {
                if (m.Msg != WM_PAINT)
                {
                    base.WndProc(ref m);
                    return;
                }
    
                using (var bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height))
                using (var bmpGraphics = Graphics.FromImage(bmp))
                {
                    // 現状のコントロール描画をBitmapにコピー
                    this.DrawToBitmap(bmp, new Rectangle(0, 0, this.Width, this.Height));
    
                    // 文字色のマッピング・入れ替え
                    System.Drawing.Imaging.ColorMap[] cm = { new System.Drawing.Imaging.ColorMap() };
                    cm[0].OldColor = SystemColors.WindowText;
                    cm[0].NewColor = Color.Red;
                    var ia = new System.Drawing.Imaging.ImageAttributes();
                    ia.SetRemapTable(cm);
    
                    // 再描画
                    bmpGraphics.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height),
                                0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, ia);
    
                    // コントロールへ描画
                    var hWnd = new HandleRef(this, m.HWnd);
                    var ps = new PAINTSTRUCT();
                    var controlHdc = BeginPaint(hWnd, ref ps);
                    using (var controlGraphics = Graphics.FromHdc(controlHdc))
                    {
                        var bsz = SystemInformation.Border3DSize;
                        controlGraphics.DrawImage(bmp, -bsz.Width + 2, -bsz.Height + 2);
                    }
                    EndPaint(hWnd, ref ps);
    
                    base.WndProc(ref m);
                }
            }
        }
    }

    using System;
    using System.Runtime.InteropServices;
    
    namespace Test
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct PAINTSTRUCT
        {
            public IntPtr hdc;
    
            public bool fErase;
    
            public RECT rcPaint;
    
            public bool fRestore;
    
            public bool fIncUpdate;
    
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] rgbReserved;
        }
    }
    
    using System.Runtime.InteropServices;
    
    namespace Test
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
    
            public int top;
    
            public int right;
    
            public int bottom;
        }
    }
    

    ところがこれを行うと、下記の実行結果のように、利用されているフォントがMS UI Gothic、MS ゴシックなどでは問題ありませんが、Meiryo UI、メイリオなどのフォントの描画にアンチエイリアスがかかっているようなものだと、文字の境界部分の色が異なるため、綺麗に色を差し替えることができません。

    DateTimePickerの仕様上むりくりなのは理解していますが、方法論は何でも構わないので、DateTimePickerの動作仕様(カーソルを当てたら年月日の位置が選択状態になるなど)は既存のまま、どのフォントが利用されていても綺麗に文字色を変更させることはできませんでしょうか?

    2020年11月27日 1:25

回答

  • キャレットの長方形を求める部分を切り出しましょう。
    private static Rectangle GetCaretRectangle(Bitmap bmp)
    {
        var baseColor = SystemColors.Highlight;
        var pixelFormat = bmp.PixelFormat;
        int pixelSize = Image.GetPixelFormatSize(pixelFormat) / 8;
        BitmapData bmpData = bmp.LockBits(
            new Rectangle(0, 0, bmp.Width, bmp.Height),
            ImageLockMode.ReadWrite,
            pixelFormat);
    
        IntPtr ptr = bmpData.Scan0;
        byte[] pixels = new byte[bmpData.Stride * bmp.Height];
        Marshal.Copy(ptr, pixels, 0, pixels.Length);
    
        var rangeStartX = -1;
        var rangeEndX = -1;
        var rangeStartY = 4;
        var rangeEndY = bmpData.Height - 4;
        for (int x = 2; x < bmpData.Width - 30; x++)
        {
            int pos = rangeStartY * bmpData.Stride + x * pixelSize;
            if (pixels[pos] == baseColor.B && pixels[pos + 1] == baseColor.G && pixels[pos + 2] == baseColor.R)
            {
                if (rangeStartX == -1)
                {
                    rangeStartX = x;
                }
                else
                {
                    rangeEndX = x;
                }
            }
        }
        for (int y = rangeStartY; y > 0; y--)
        {
            int pos = y * bmpData.Stride + rangeStartX * pixelSize;
            if (pixels[pos] == baseColor.B && pixels[pos + 1] == baseColor.G && pixels[pos + 2] == baseColor.R)
            {
                rangeStartY = y;
            }
        }
        bmp.UnlockBits(bmpData);
        return Rectangle.FromLTRB(rangeStartX, rangeStartY, rangeEndX + 1, rangeEndY); 
    }
    
    WmPaint の部分はこうなります。
    private void WmPaint(ref Message m)
    {
        var cs = this.ClientSize;
        var dti = new DATETIMEPICKERINFO();
        var ret = SendMessage(this.Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, dti);
        var bsz = SystemInformation.Border3DSize;
        var canvas = new Rectangle(bsz.Width, bsz.Height, dti.rcButton.Left - bsz.Width, cs.Height - bsz.Height * 2);
    
        using (var bmpBack = CreateNativeBitmap())
        using (var bmpFore = CreateNagetiveBitmap(bmpBack))
        {
    
            BitBlt(bmpBack, BackColor, canvas, SRCAND);
            BitBlt(bmpFore, ForeColor, canvas, SRCAND);
    
            using (var bmp = new Bitmap(cs.Width, cs.Height))
            {
                using (var g = Graphics.FromImage(bmp))
                {
                    IntPtr hdc = g.GetHdc();
                    BitBlt(hdc, bmpBack, SRCCOPY);
                    BitBlt(hdc, bmpFore, canvas, canvas.Location, SRCPAINT);
    
                    // フォーカスがあるとき、キャレットの部分のみ転送
                    if (this.ContainsFocus)
                    {
                        using (var bmpOrg = CreateNativeBitmap())
                        {
                            Rectangle caret = GetCaretRectangle(bmpOrg);
                            BitBlt(hdc, bmpOrg, caret, caret.Location, SRCCOPY);
                        }
                    }
                    g.ReleaseHdc();
                }
    
                if (m.WParam == IntPtr.Zero)
                {
                    // コントロールに描画
                    var ps = new PAINTSTRUCT();
                    var controlHdc = BeginPaint(m.HWnd, ref ps);
                    using (var controlGraphics = Graphics.FromHdc(controlHdc))
                    {
                        controlGraphics.DrawImage(bmp, 0, 0);
                    }
                    EndPaint(m.HWnd, ref ps);
                }
                else
                {
                    // hdc に描画
                    using (var controlGraphics = Graphics.FromHdc(m.WParam))
                    {
                        controlGraphics.DrawImage(bmp, 0, 0);
                    }
                }
            }
        }
    }
    

    • 回答としてマーク takiru 2020年12月9日 0:28
    2020年12月7日 9:36

すべての返信

  • DateTimePickerの上にTextBoxをかぶせてみる。

    OSやテーマの違いで簡単に破綻するのでWPFのDatePickerをElementHostで表示させた方が現実的ですけどね。

    namespace WindowsFormsApp1
    {
        using System;
        using System.ComponentModel;
        using System.Drawing;
        using System.Windows.Forms;
    
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
    
                System.Windows.Forms.Integration.ElementHost host
                    = new System.Windows.Forms.Integration.ElementHost()
                    {
                        Child = new System.Windows.Controls.DatePicker()
                        {
                            Foreground = System.Windows.Media.Brushes.Red,
                            FontFamily = new System.Windows.Media.FontFamily("Meiryo"),
                            FontSize = 12,
                        },
                        Top = 30,
                        Height = 30,
    
                    };
                this.Controls.Add(host);
    
                DateTimePickerEx dp = new DateTimePickerEx()
                {
                    ForeColor = System.Drawing.Color.Red,
                    Font = new Font(new FontFamily("Meiryo"), 11f),
                };
                this.Controls.Add(dp);
    
            }
        }
    
        class DateTimePickerEx : DateTimePicker
        {
            private TextBox txb;
            private CheckBox chk;
            private Label lbl;
            private bool lastIsNumber;
            
    
            public DateTimePickerEx()
            {
                lbl = new Label();
                lbl.AutoSize = true;
                lbl.Top = -100;
                this.Controls.Add(lbl);
                
                chk = new CheckBox();
                chk.Text = "";
                chk.AutoSize = true;
                chk.CheckedChanged += Chk_CheckedChanged;
                chk.GotFocus += (s, e) => this.Checked = chk.Checked;
    
                txb = new TextBox();
                txb.BackColor = this.BackColor;
                txb.BorderStyle = BorderStyle.None;
                txb.Font = this.Font;
                txb.AutoSize = true;
    
                txb.ImeMode = ImeMode.Disable;
                txb.KeyDown += Txb_KeyDown;
                txb.KeyPress += Txb_KeyPress;
                txb.MouseClick += Txb_MouseClick;
    
                this.Controls.Add(chk);
                this.Controls.Add(txb);
                Relocate();
            }
    
            private void Chk_CheckedChanged(object sender, EventArgs e)
            {
                if (chk.Checked)
                {
                    txb.ReadOnly = false;
                }
                else
                {
                    txb.ReadOnly = true;
                    txb.ForeColor = SystemColors.GrayText;
                }
            }
    
            private void ExpandTextBoxSelection()
            {
                var sel = txb.Text.Substring(txb.SelectionStart, txb.SelectionLength);
                int start = txb.SelectionStart;
                int end = txb.SelectionStart;
    
                if (start == txb.Text.Length)
                {
                    start--;
                    end = start;
                }
    
                while (start > 0)
                {
                    char c = txb.Text[start - 1];
    
                    if (char.IsNumber(c) || c == ' ')
                    {
                        start--;
                    }
                    else
                    {
                        break;
                    }
                }
    
                end = start;
                while (end < txb.Text.Length)
                {
                    char c = txb.Text[end];
    
                    if (char.IsNumber(c))
                    {
                        end++;
                    }
                    else
                    {
                        break;
                    }
                }
    
                this.txb.SelectionStart = start;
                this.txb.SelectionLength = end - start;
            }
    
            private void Txb_MouseClick(object sender, MouseEventArgs e)
            {
                ExpandTextBoxSelection();
            }
    
            private void Txb_KeyDown(object sender, KeyEventArgs e)
            {
                e.Handled = true;
    
                int start = txb.SelectionStart;
                int len = txb.SelectionLength;
                switch (e.KeyCode)
                {
                case Keys.Up:
                    {
                        if (txb.ReadOnly)
                        {
                            break;
                        }
                        int i = int.Parse(txb.SelectedText);
                        var s = (i + 1).ToString().PadLeft(txb.SelectionLength);
    
                        s = txb.Text.Substring(0, start) + s + txb.Text.Substring(start + len);
                        try
                        {
                            this.Text = s;
                            txb.Text = s;
                            txb.Select(start, len);
                        }
                        catch
                        {
                        }
    
                        break;
                    }
                case Keys.Down:
                    {
                        if (txb.ReadOnly)
                        {
                            break;
                        }
                        if (e.Alt && !e.Control && !e.Shift)
                        {
    
                        }
                        else
                        {
                            int i = int.Parse(txb.SelectedText);
                            string s = "";
                            if (i > 1)
                            {
                                s = (i - 1).ToString().PadLeft(txb.SelectionLength).PadLeft(len, ' ');
                            }
    
                            s = txb.Text.Substring(0, start) + s + txb.Text.Substring(start + len);
                            try
                            {
                                this.Text = s;
                                txb.Text = s;
                                txb.Select(start, len);
                            }
                            catch
                            {
                            }
                        }
                        break;
                    }
                case Keys.Left:
                    {
                        bool flag = false;
                        txb.SelectionLength = 0;
                        for (; ; )
                        {
                            if (--start < 0)
                            {
                                if (flag)
                                {
                                    throw new InvalidOperationException();
                                }
                                start = txb.Text.Length - 1;
                                txb.SelectionLength = 0;
                                flag = true;
                            }
                            char c = txb.Text[start];
                            if (char.IsNumber(c))
                            {
                                txb.SelectionStart = start;
                                txb.SelectionLength = 0;
                                ExpandTextBoxSelection();
                                break;
                            }
    
                        }
                        break;
                    }
                case Keys.Right:
                    {
                        bool flag = false;
    
                        start = txb.SelectionStart + txb.SelectionLength;
                        txb.SelectionLength = 0;
                        for (; ; )
                        {
                            if (++start > txb.Text.Length - 1)
                            {
                                if (flag)
                                {
                                    throw new InvalidOperationException();
                                }
                                start = 0;
                                txb.SelectionLength = 0;
                                flag = true;
                            }
                            char c = txb.Text[start];
                            if (char.IsNumber(c))
                            {
                                txb.SelectionStart = start;
                                txb.SelectionLength = 0;
                                ExpandTextBoxSelection();
                                break;
                            }
    
                        }
                        break;
                    }
                default:
                    break;
                }
    
            }
    
            private void Txb_KeyPress(object sender, KeyPressEventArgs e)
            {
                e.Handled = true;
    
                if (!txb.ReadOnly && char.IsNumber(e.KeyChar))
                {
                    int start = txb.SelectionStart;
                    int len = txb.SelectionLength;
    
                    string s;
                    if (!lastIsNumber || char.IsNumber(txb.SelectedText[0]))
                    {
                        s = e.KeyChar.ToString().PadLeft(len, ' ');
                    }
                    else
                    {
                        s = (txb.SelectedText + e.KeyChar).Substring(1);
                    }
    
                    s = txb.Text.Substring(0, start) + s + txb.Text.Substring(start + len);
                    try
                    {
                        this.Text = s;
                        txb.Text = s;
                        txb.Select(start, len);
                        lastIsNumber = true;
                        return;
                    }
                    catch
                    {
                    }
    
                }
    
            }
    
    
            protected override void OnHandleCreated(EventArgs e)
            {
                base.OnHandleCreated(e);
    
                txb.Text = this.Text;
    
                chk.Checked = this.Checked;
    
                Relocate();
            }
            protected override void OnFontChanged(EventArgs e)
            {
                base.OnFontChanged(e);
                this.txb.Font = this.Font;
                this.lbl.Font = this.Font;
                Relocate();
            }
            protected override void OnResize(EventArgs e)
            {
                base.OnResize(e);
                Relocate();
            }
            protected override void OnValueChanged(EventArgs eventargs)
            {
                base.OnValueChanged(eventargs);
                this.txb.Text = this.Text;
                Relocate();
            }
            protected override void OnTextChanged(EventArgs e)
            {
                base.OnTextChanged(e);
                Relocate();
            }
    
            private void Relocate()
            {
                lbl.Text = this.Text;
    
                this.txb.Width = lbl.Width;            
                this.txb.Left = 2;
                this.txb.Top = (this.Height - this.txb.Height) / 2;
    
                if (this.ShowCheckBox)
                {
                    this.chk.Visible = true;
                    this.chk.Left = 0;
                    this.chk.Left = 2;
                    this.chk.Top = (this.Height - this.chk.Height) / 2;
                    this.txb.Left = 2 + this.chk.Width;
                }
                else
                {
                    this.chk.Visible = false;
    
                }
            }
    
            [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Always)]
            [System.ComponentModel.Browsable(true)]
            public override Color ForeColor
            {
                get
                {
                    if (this.txb == null)
                    {
                        return base.ForeColor;
                    }
                    return this.txb.ForeColor;
                }
                set
                {
                    this.txb.ForeColor = value;
                }
            }
    
            protected override void OnGotFocus(EventArgs e)
            {
    
                System.Diagnostics.Debug.WriteLine("GotFocus");
                base.OnGotFocus(e);
    
                txb.Focus();
                txb.SelectionStart = 0;
                ExpandTextBoxSelection();
    
            }
            protected override void OnDropDown(EventArgs eventargs)
            {
                //txb.SelectionStart = 0;
                //txb.SelectionLength = 0;
    
                this.chk.Checked = true;
                base.OnDropDown(eventargs);
            }
    
        }
    }
    #WinUI3が来たらまた変わるけど

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2020年11月27日 23:44
  • takiruさん、こんにちは。フォーラムオペレーターのKumoです。 
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    ご質問いただいた件ですが、その後いかがでしょうか。
    gekkaさんから寄せられた投稿はお役に立ちましたか。

    参考になった投稿には [回答としてマーク] をお願い致します。

    設定いただくことで、
    他のユーザーもお役に立つ回答を見つけやすくなります。

    お手数ですが、ご協力の程どうかよろしくお願いいたします。

    MSDN/ TechNet Community Support Kumo ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    2020年12月1日 8:33
    モデレータ
  • ForeColor で塗りつぶしたビットマップを用意して OR 演算で BitBlt してみました。
    キャレットの色まで変わりますが・・・

    namespace WindowsFormsApp3
    {
        using System;
        using System.ComponentModel;
        using System.Drawing;
        using System.Runtime.InteropServices;
        using System.Windows.Forms;
    
        class DateTimePickerEx : DateTimePicker
        {
            [Browsable(true)]
            public override Color ForeColor { get => base.ForeColor; set => base.ForeColor = value; }
    
            private const int WM_PAINT = 0x000F;
            private const int SRCPAINT = 0x00EE0086;
    
            [DllImport("gdi32.dll")]
            private static extern int BitBlt(IntPtr hdc, 
                                      int nXDest, int nYDest,
                                      int nWidth, int nHeight,
                                      IntPtr hdcSrc,
                                      int nXSrc, int nYSrc, 
                                      int dwRop);
    
            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                public int Left, Top, Right, Bottom;
                public Rectangle ToRectangle()
                {
                    return Rectangle.FromLTRB(Left, Top, Right, Bottom);
                }
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct PAINTSTRUCT
            {
                public IntPtr hdc;
                public bool fErase;
                public RECT rcPaint;
                public bool fRestore;
                public bool fIncUpdate;
                public int reserved1;
                public int reserved2;
                public int reserved3;
                public int reserved4;
                public int reserved5;
                public int reserved6;
                public int reserved7;
                public int reserved8;
            }
    
            [DllImport("user32.dll")]
            private static extern IntPtr BeginPaint(HandleRef hWnd, ref PAINTSTRUCT lpPaint);
    
            [DllImport("user32.dll")]
            private static extern bool EndPaint(HandleRef hWnd, ref PAINTSTRUCT lpPaint);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern bool DeleteDC(IntPtr hdc);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
    
            [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool DeleteObject([In] IntPtr hObject);
    
            [StructLayout(LayoutKind.Sequential)]
            private class DATETIMEPICKERINFO
            {
                public int cbSize;
                public RECT rcCheck;
                public int stateCheck;
                public RECT rcButton;
                public int stateButton;
                public IntPtr hwndEdit;
                public IntPtr hwndUD;
                public IntPtr hwndDropDown;
    
                public DATETIMEPICKERINFO()
                {
                    cbSize = Marshal.SizeOf(this);
                }
            }
    
            private const int DTM_FIRST = 0x1000;
            private const int DTM_GETDATETIMEPICKERINFO = DTM_FIRST + 14;
    
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            private static extern IntPtr SendMessage(IntPtr hWnd, 
                                                    int Msg, 
                                                    IntPtr wParam,
                                                    DATETIMEPICKERINFO lParam);
    
            protected override void WndProc(ref Message m)
            {
                switch (m.Msg)
                {
                    case WM_PAINT:
                        WmPaint(ref m);
                        break;
                    default:
                        base.WndProc(ref m);
                        break;
                }
            }
    
            private void WmPaint(ref Message m)
            {
                var cs = this.ClientSize;
                
                using (var bmpForeColor = new Bitmap(cs.Width, cs.Height))
                {
                    // ForeColor で塗りつぶしたビットマップを作成
                    using (var g = Graphics.FromImage(bmpForeColor))
                    {
                        using (var b = new SolidBrush(ForeColor))
                        {
                            g.FillRectangle(b, 0, 0, cs.Width, cs.Height);
                        }
                    }
    
                    // コントロールのビットマップを作成
                    using (var bmp = new Bitmap(cs.Width, cs.Height))
                    {
                        using (var g = Graphics.FromImage(bmp))
                        {
                            // WM_PAINT を送って描画してもらう
                            var hdc = g.GetHdc();
                            var pm = Message.Create(m.HWnd, WM_PAINT, hdc, IntPtr.Zero);
                            base.DefWndProc(ref pm);
    
                            // 転送元の hdc を用意
                            IntPtr hdcForeColor = CreateCompatibleDC(hdc);
                            IntPtr hBitmap = bmpForeColor.GetHbitmap();
                            IntPtr hbmpOld = SelectObject(hdcForeColor, hBitmap);
    
                            // ボタンの位置/枠のサイズを求める
                            var dti = new DATETIMEPICKERINFO();
                            var ret = SendMessage(this.Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, dti);
                            var bsz = SystemInformation.Border3DSize;
    
                            // ボタンと枠を除いて BitBlt (OR演算)
                            BitBlt(hdc, bsz.Width, bsz.Height, dti.rcButton.Left, 
                                cs.Height - bsz.Height * 2, hdcForeColor, 0, 0, SRCPAINT);
    
                            // 転送元の DC を削除
                            SelectObject(hdcForeColor, hbmpOld);
                            DeleteObject(hBitmap);
                            DeleteDC(hdcForeColor);
    
                            // hdc 解放
                            g.ReleaseHdc();
                        }
    
                        // コントロールに描画
                        var hWnd = new HandleRef(this, m.HWnd);
                        var ps = new PAINTSTRUCT();
                        var controlHdc = BeginPaint(hWnd, ref ps);
                        using (var controlGraphics = Graphics.FromHdc(controlHdc))
                        {
                            controlGraphics.DrawImage(bmp, 0, 0);
                        }
                        EndPaint(hWnd, ref ps);
                    }
                }
            }
        }
    }
    



    • 編集済み KOZ6.0 2020年12月3日 7:48
    2020年12月3日 7:35
  • ご回答ありがとうございます。

    私も同じような結論に至り、試行錯誤しておりました。
    DATETIMEPICKERINFOやBitBlt()は知らなかったので参考にさせていただきます。
    左のチェックボックスや右のドロップダウン/カレンダーアイコンなどの領域幅が分からず
    頓挫していましたが、これで綺麗に取り除くことができそうです。

    選択状態にある色までも変わってしまうことについても、現在、予め色を変更する前の状態から
    Y=3あたりの色を走査して領域を見つけ出しておいて(SystemColors.Highlightの部分)、
    色を変更した後にその部分のピクセルだけ差し替え直す、という手段を取っています。

    DATETIMEPICKERINFOなどでその領域なども綺麗に取れると素敵かもしれませんが、
    もし選択領域の部分も綺麗に差し替え直す方法があるようでしたらご教示ください。

    実際は背景色も変更しようとしているので、BitBlt()で対応できるのか定かではありませんが、
    教えていただいた内容を参考に、再度試行錯誤してみようかと思います。

    2020年12月7日 1:30
  • キャレットの四角形が取得できれば良いのですが、私はその方法を知りません。GetCaretPos で位置は取れるかもですが・・・
    あとで takiru さんが採用した判定方法を教えてくださいね。

    背景色も反映するのであれば、前景色用と背景色用2つのビットマップを作成することになります。
    (1) フォントの色を反映+背景色を黒
    元画像を NOTSRCCOPY で反転させ、ForeColor と SRCAND する
    (2) フォントの色を黒+背景色を反映
    元画像と BackColor を SRCAND する
    (3) 2つのビットマップを OR演算(SRCPAINT)

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    class DateTimePickerEx : DateTimePicker
    {
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        public override Color ForeColor { get => base.ForeColor; set => base.ForeColor = value; }
    
        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        public override Color BackColor { get => base.BackColor; set => base.BackColor = value; }
    
        private const int WM_PAINT      = 0x000F;
    
        private const int SRCPAINT      = 0x00EE0086;
        private const int SRCAND        = 0x8800C6;  
        private const int NOTSRCCOPY    = 0x330008;  
        private const int SRCCOPY       = 0xCC0020;  
    
        [DllImport("gdi32.dll")]
        private static extern int BitBlt(IntPtr hdc,
                                  int nXDest, int nYDest,
                                  int nWidth, int nHeight,
                                  IntPtr hdcSrc,
                                  int nXSrc, int nYSrc,
                                  int dwRop);
    
        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left, Top, Right, Bottom;
            public Rectangle ToRectangle()
            {
                return Rectangle.FromLTRB(Left, Top, Right, Bottom);
            }
        }
    
        [StructLayout(LayoutKind.Sequential)]
        private struct PAINTSTRUCT
        {
            public IntPtr hdc;
            public bool fErase;
            public RECT rcPaint;
            public bool fRestore;
            public bool fIncUpdate;
            public int reserved1;
            public int reserved2;
            public int reserved3;
            public int reserved4;
            public int reserved5;
            public int reserved6;
            public int reserved7;
            public int reserved8;
        }
    
        [DllImport("user32.dll")]
        private static extern IntPtr BeginPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
    
        [DllImport("user32.dll")]
        private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
    
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
    
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern bool DeleteDC(IntPtr hdc);
    
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
    
        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeleteObject([In] IntPtr hObject);
    
        [StructLayout(LayoutKind.Sequential)]
        private class DATETIMEPICKERINFO
        {
            public int cbSize;
            public RECT rcCheck;
            public int stateCheck;
            public RECT rcButton;
            public int stateButton;
            public IntPtr hwndEdit;
            public IntPtr hwndUD;
            public IntPtr hwndDropDown;
    
            public DATETIMEPICKERINFO()
            {
                cbSize = Marshal.SizeOf(this);
            }
        }
    
        private const int DTM_FIRST = 0x1000;
        private const int DTM_GETDATETIMEPICKERINFO = DTM_FIRST + 14;
    
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr hWnd,
                                                int Msg,
                                                IntPtr wParam,
                                                DATETIMEPICKERINFO lParam);
    
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_PAINT:
                    WmPaint(ref m);
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
    
        private static Bitmap CreateColorBitmap(Color color, Size size)
        {
            var bmp = new Bitmap(size.Width, size.Height);
            using (var g = Graphics.FromImage(bmp))
            {
                g.Clear(color);
            }
            return bmp;
        }
    
        private static void BitBlt(IntPtr hdc, Color color, Rectangle rectangle, int dwRop)
        {
            using (Bitmap bmp = CreateColorBitmap(color, rectangle.Size))
            {
                BitBlt(hdc, bmp, rectangle, dwRop);
            }
        }
    
        private static void BitBlt(Bitmap bmp, Color color, Rectangle rectangle, int dwRop)
        {
            using (var g = Graphics.FromImage(bmp))
            {
                IntPtr hdc = g.GetHdc();
                BitBlt(hdc, bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), SRCCOPY);
                BitBlt(hdc, color, rectangle, dwRop);
                g.ReleaseHdc();
            }
        }
    
        private static void BitBlt(IntPtr hdc, Bitmap bmp)
        {
            BitBlt(hdc, bmp, SRCCOPY);
        }
    
        private static void BitBlt(IntPtr hdc, Bitmap bmp, int dwRop)
        {
            BitBlt(hdc, bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), dwRop);
        }
    
        private static void BitBlt(IntPtr hdc, Bitmap bmp, Rectangle rectangle, int dwRop)
        {
            BitBlt(hdc, bmp, rectangle, new Point(0, 0), dwRop);
        }
    
        private static void BitBlt(IntPtr hdc, Bitmap bmp, Rectangle rectangle, Point point, int dwRop)
        {
            IntPtr hdcSrc = CreateCompatibleDC(hdc);
            IntPtr hBitmap = bmp.GetHbitmap();
            IntPtr hbmpOld = SelectObject(hdcSrc, hBitmap);
    
            BitBlt(hdc, 
                rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height, 
                hdcSrc, point.X, point.Y, dwRop);
    
            SelectObject(hdcSrc, hbmpOld);
            DeleteObject(hBitmap);
            DeleteDC(hdcSrc);
        }
    
        private static void BitBlt(Bitmap bmpDst, Bitmap bmpSrc, int dwRop)
        {
            using (var g = Graphics.FromImage(bmpDst))
            {
                IntPtr hdc = g.GetHdc();
                BitBlt(hdc, bmpDst);
                BitBlt(hdc, bmpSrc, dwRop);
                g.ReleaseHdc();
            }
        }
    
        private Bitmap CreateNativeBitmap()
        {
            var cs = this.ClientSize;
            var bmp = new Bitmap(cs.Width, cs.Height);
            using (var g = Graphics.FromImage(bmp))
            {
                var hdc = g.GetHdc();
                var pm = Message.Create(this.Handle, WM_PAINT, hdc, IntPtr.Zero);
                base.DefWndProc(ref pm);
                g.ReleaseHdc();
            }
            return bmp;
        }
    
        private static Bitmap CreateNagetiveBitmap(Bitmap bmp)
        {
            var bmpDest = new Bitmap(bmp.Width, bmp.Height);
            using (var g = Graphics.FromImage(bmpDest))
            {
                var hdc = g.GetHdc();
                BitBlt(hdc, bmp, NOTSRCCOPY);
                g.ReleaseHdc();
            }
            return bmpDest;
        }
    
        private void WmPaint(ref Message m)
        {
                var cs = this.ClientSize;
            var dti = new DATETIMEPICKERINFO();
            var ret = SendMessage(this.Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, dti);
            var bsz = SystemInformation.Border3DSize;
            var canvas = new Rectangle(bsz.Width, bsz.Height, dti.rcButton.Left - bsz.Width, cs.Height - bsz.Height * 2);
    
            using (var bmpBack = CreateNativeBitmap())
            using (var bmpFore = CreateNagetiveBitmap(bmpBack))
            {
                BitBlt(bmpBack, BackColor, canvas, SRCAND);
                BitBlt(bmpFore, ForeColor, canvas, SRCAND);
    
                using (var bmp = new Bitmap(cs.Width, cs.Height))
                {
                    using (var g = Graphics.FromImage(bmp))
                    {
                        IntPtr hdc = g.GetHdc();
                        BitBlt(hdc, bmpBack, SRCCOPY);
                        BitBlt(hdc, bmpFore, canvas, canvas.Location, SRCPAINT);
                        g.ReleaseHdc();
                    }
    
                    if (m.WParam == IntPtr.Zero)
                    {
                        // コントロールに描画
                        var ps = new PAINTSTRUCT();
                        var controlHdc = BeginPaint(m.HWnd, ref ps);
                        using (var controlGraphics = Graphics.FromHdc(controlHdc))
                        {
                            controlGraphics.DrawImage(bmp, 0, 0);
                        }
                        EndPaint(m.HWnd, ref ps);
                    }
                    else
                    {
                        // hdc に描画
                        using (var controlGraphics = Graphics.FromHdc(m.WParam))
                        {
                            controlGraphics.DrawImage(bmp, 0, 0);
                        }
                    }
                }
            }
        }
    }
    


    2020年12月7日 2:22
  • 再度ご回答ありがとうございます。

    すごい綺麗に前景色、背景色を変更できるんですね!
    私がやっていたのは、よく分かってないまま、ColorMatrx使って無理やりゴチャゴチャやってました。

    大変稚拙で恥ずかしいですが、選択領域の色も変えるコードを、教えて頂いたコードに組み込んでみました。
    画像処理の制御があまりわかってないのとムリクリなので、変数名もテキトーになっていて読みづらいかもしれませんが、
    下記コードのような状態です。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace Test
    {
        class DateTimePickerEx : DateTimePicker
        {
            [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
            public override Color ForeColor { get => base.ForeColor; set => base.ForeColor = value; }
    
            [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
            public override Color BackColor { get => base.BackColor; set => base.BackColor = value; }
    
            private const int WM_PAINT = 0x000F;
    
            private const int SRCPAINT = 0x00EE0086;
            private const int SRCAND = 0x8800C6;
            private const int NOTSRCCOPY = 0x330008;
            private const int SRCCOPY = 0xCC0020;
    
            [DllImport("gdi32.dll")]
            private static extern int BitBlt(IntPtr hdc,
                                      int nXDest, int nYDest,
                                      int nWidth, int nHeight,
                                      IntPtr hdcSrc,
                                      int nXSrc, int nYSrc,
                                      int dwRop);
    
            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                public int Left, Top, Right, Bottom;
                public Rectangle ToRectangle()
                {
                    return Rectangle.FromLTRB(Left, Top, Right, Bottom);
                }
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct PAINTSTRUCT
            {
                public IntPtr hdc;
                public bool fErase;
                public RECT rcPaint;
                public bool fRestore;
                public bool fIncUpdate;
                public int reserved1;
                public int reserved2;
                public int reserved3;
                public int reserved4;
                public int reserved5;
                public int reserved6;
                public int reserved7;
                public int reserved8;
            }
    
            [DllImport("user32.dll")]
            private static extern IntPtr BeginPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
    
            [DllImport("user32.dll")]
            private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern bool DeleteDC(IntPtr hdc);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
    
            [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool DeleteObject([In] IntPtr hObject);
    
            [StructLayout(LayoutKind.Sequential)]
            private class DATETIMEPICKERINFO
            {
                public int cbSize;
                public RECT rcCheck;
                public int stateCheck;
                public RECT rcButton;
                public int stateButton;
                public IntPtr hwndEdit;
                public IntPtr hwndUD;
                public IntPtr hwndDropDown;
    
                public DATETIMEPICKERINFO()
                {
                    cbSize = Marshal.SizeOf(this);
                }
            }
    
            private const int DTM_FIRST = 0x1000;
            private const int DTM_GETDATETIMEPICKERINFO = DTM_FIRST + 14;
    
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            private static extern IntPtr SendMessage(IntPtr hWnd,
                                                    int Msg,
                                                    IntPtr wParam,
                                                    DATETIMEPICKERINFO lParam);
    
            protected override void WndProc(ref Message m)
            {
                switch (m.Msg)
                {
                    case WM_PAINT:
                        WmPaint(ref m);
                        break;
                    default:
                        base.WndProc(ref m);
                        break;
                }
            }
    
            private static Bitmap CreateColorBitmap(Color color, Size size)
            {
                var bmp = new Bitmap(size.Width, size.Height);
                using (var g = Graphics.FromImage(bmp))
                {
                    g.Clear(color);
                }
                return bmp;
            }
    
            private static void BitBlt(IntPtr hdc, Color color, Rectangle rectangle, int dwRop)
            {
                using (Bitmap bmp = CreateColorBitmap(color, rectangle.Size))
                {
                    BitBlt(hdc, bmp, rectangle, dwRop);
                }
            }
    
            private static void BitBlt(Bitmap bmp, Color color, Rectangle rectangle, int dwRop)
            {
                using (var g = Graphics.FromImage(bmp))
                {
                    IntPtr hdc = g.GetHdc();
                    BitBlt(hdc, bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), SRCCOPY);
                    BitBlt(hdc, color, rectangle, dwRop);
                    g.ReleaseHdc();
                }
            }
    
            private static void BitBlt(IntPtr hdc, Bitmap bmp)
            {
                BitBlt(hdc, bmp, SRCCOPY);
            }
    
            private static void BitBlt(IntPtr hdc, Bitmap bmp, int dwRop)
            {
                BitBlt(hdc, bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), dwRop);
            }
    
            private static void BitBlt(IntPtr hdc, Bitmap bmp, Rectangle rectangle, int dwRop)
            {
                BitBlt(hdc, bmp, rectangle, new Point(0, 0), dwRop);
            }
    
            private static void BitBlt(IntPtr hdc, Bitmap bmp, Rectangle rectangle, Point point, int dwRop)
            {
                IntPtr hdcSrc = CreateCompatibleDC(hdc);
                IntPtr hBitmap = bmp.GetHbitmap();
                IntPtr hbmpOld = SelectObject(hdcSrc, hBitmap);
    
                BitBlt(hdc,
                    rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height,
                    hdcSrc, point.X, point.Y, dwRop);
    
                SelectObject(hdcSrc, hbmpOld);
                DeleteObject(hBitmap);
                DeleteDC(hdcSrc);
            }
    
            private static void BitBlt(Bitmap bmpDst, Bitmap bmpSrc, int dwRop)
            {
                using (var g = Graphics.FromImage(bmpDst))
                {
                    IntPtr hdc = g.GetHdc();
                    BitBlt(hdc, bmpDst);
                    BitBlt(hdc, bmpSrc, dwRop);
                    g.ReleaseHdc();
                }
            }
    
            private Bitmap CreateNativeBitmap()
            {
                var cs = this.ClientSize;
                var bmp = new Bitmap(cs.Width, cs.Height);
                using (var g = Graphics.FromImage(bmp))
                {
                    var hdc = g.GetHdc();
                    var pm = Message.Create(this.Handle, WM_PAINT, hdc, IntPtr.Zero);
                    base.DefWndProc(ref pm);
                    g.ReleaseHdc();
                }
                return bmp;
            }
    
            private static Bitmap CreateNagetiveBitmap(Bitmap bmp)
            {
                var bmpDest = new Bitmap(bmp.Width, bmp.Height);
                using (var g = Graphics.FromImage(bmpDest))
                {
                    var hdc = g.GetHdc();
                    BitBlt(hdc, bmp, NOTSRCCOPY);
                    g.ReleaseHdc();
                }
                return bmpDest;
            }
    
            private void WmPaint(ref Message m)
            {
                var bsz = SystemInformation.Border3DSize;
                var cs = this.ClientSize;
                var dti = new DATETIMEPICKERINFO();
                var ret = SendMessage(this.Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, dti);
                var canvas = new Rectangle(bsz.Width, bsz.Height, dti.rcButton.Left - bsz.Width, cs.Height - bsz.Height * 2);
    
                using (var bmpBack = CreateNativeBitmap())
                using (var bmpFore = CreateNagetiveBitmap(bmpBack))
                {
                    BitBlt(bmpBack, BackColor, canvas, SRCAND);
                    BitBlt(bmpFore, ForeColor, canvas, SRCAND);
    
                    using (var bmp = new Bitmap(cs.Width, cs.Height))
                    {
                        using (var g = Graphics.FromImage(bmp))
                        {
                            IntPtr hdc = g.GetHdc();
                            BitBlt(hdc, bmpBack, SRCCOPY);
                            BitBlt(hdc, bmpFore, canvas, canvas.Location, SRCPAINT);
                            g.ReleaseHdc();
                        }
    
                        if (m.WParam == IntPtr.Zero)
                        {
                            // コントロールに描画
                            var ps = new PAINTSTRUCT();
                            var controlHdc = BeginPaint(m.HWnd, ref ps);
                            using (var controlGraphics = Graphics.FromHdc(controlHdc))
                            {
                                //controlGraphics.DrawImage(bmp, 0, 0);
    
                                // ===== ここから
                                if (this.Focused)
                                {
                                    var baseColor = SystemColors.Highlight;
                                    var bmp2 = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
                                    this.DrawToBitmap(bmp2, new Rectangle(0, 0, this.Width, this.Height));
                                    var pixelFormat = bmp2.PixelFormat;
                                    int pixelSize = Image.GetPixelFormatSize(pixelFormat) / 8;
    
                                    //Bitmapをロックする
                                    BitmapData bmpData = bmp2.LockBits(
                                        new Rectangle(0, 0, bmp2.Width, bmp2.Height),
                                        ImageLockMode.ReadWrite,
                                        pixelFormat);
    
                                    //ピクセルデータをバイト型配列で取得する
                                    IntPtr ptr = bmpData.Scan0;
                                    byte[] pixels = new byte[bmpData.Stride * bmp2.Height];
                                    Marshal.Copy(ptr, pixels, 0, pixels.Length);
    
                                    // 選択反転色があるX領域を確定する(1以外の文字は、最初の文字や最後の文字で反転色がないエリアがある)
                                    // Y領域はメイリオだと3、Meiryo UIだと4だったのでとりあえず4
                                    var rangeStartX = -1;
                                    var rangeEndX = -1;
                                    var rangeStartY = bsz.Height + 2;   // 4
                                    var rangeEndY = bmpData.Height - 4;
                                    for (int x = bsz.Width; x <= dti.rcButton.Left - bsz.Width; x++)
                                    {
                                        int pos = rangeStartY * bmpData.Stride + x * pixelSize;
                                        if (pixels[pos] == baseColor.B && pixels[pos + 1] == baseColor.G && pixels[pos + 2] == baseColor.R)
                                        {
                                            if (rangeStartX == -1)
                                            {
                                                rangeStartX = x;
                                            }
                                            else
                                            {
                                                rangeEndX = x;
                                            }
                                        }
                                    }
                                    // Yは3からだったり4からだったりしたので一番上を見つける
                                    for (int y = rangeStartY; y > bsz.Height; y--)
                                    {
                                        int pos = y * bmpData.Stride + rangeStartX * pixelSize;
                                        if (pixels[pos] == baseColor.B && pixels[pos + 1] == baseColor.G && pixels[pos + 2] == baseColor.R)
                                        {
                                            rangeStartY = y;
                                        }
                                    }
    
                                    // 選択反転色の範囲を取得
                                    List<(int, int)> posList = new List<(int, int)>();
                                    if (rangeStartX > -1)
                                    {
                                        for (int y = rangeStartY; y < rangeEndY; y++)
                                        {
                                            int startPos = y * bmpData.Stride + rangeStartX * pixelSize;
                                            int endPos = y * bmpData.Stride + rangeEndX * pixelSize;
                                            posList.Add((startPos, endPos + 3));
                                        }
                                    }
    
                                    // 選択反転色のイメージを取得
                                    var rangeByte = new List<byte>();
                                    var rangePos = new List<int>();
                                    var map = new Dictionary<int, byte>();
                                    if (posList.Count > 0)
                                    {
                                        foreach (var item in posList)
                                        {
                                            for (var i = item.Item1; i <= item.Item2; i++)
                                            {
                                                rangeByte.Add(pixels[i]);
                                                rangePos.Add(i);
                                            }
                                        }
    
                                        // 位置とbyteをマッピング
                                        for (var i = 0; i < rangePos.Count; i++)
                                        {
                                            map.Add(rangePos[i], rangeByte[i]);
                                        }
                                    }
                                    //ロックを解除する
                                    bmp2.UnlockBits(bmpData);
    
                                    // Bitmapをロックする
                                    BitmapData bmpData2 = bmp.LockBits(
                                        new Rectangle(0, 0, bmp.Width, bmp.Height),
                                        ImageLockMode.ReadWrite,
                                        pixelFormat);
    
                                    // 現在の画像を適用?
                                    IntPtr ptr2 = bmpData2.Scan0;
                                    byte[] pixels2 = new byte[bmpData2.Stride * bmp.Height];
                                    Marshal.Copy(ptr2, pixels2, 0, pixels2.Length);
    
                                    // マッピングされたピクセルデータを差し替える
                                    foreach (var kvp in map)
                                    {
                                        pixels2[kvp.Key] = kvp.Value;
                                    }
                                    //ピクセルデータを元に戻す
                                    Marshal.Copy(pixels2, 0, ptr2, pixels2.Length);
    
                                    //ロックを解除する
                                    bmp.UnlockBits(bmpData2);
                                }
                                // ===== ここまで
    
                                controlGraphics.DrawImage(bmp, 0, 0);
    
                            }
                            EndPaint(m.HWnd, ref ps);
                        }
                        else
                        {
                            // hdc に描画
                            using (var controlGraphics = Graphics.FromHdc(m.WParam))
                            {
                                controlGraphics.DrawImage(bmp, 0, 0);
                            }
                        }
                    }
                }
            }
        }
    }






    • 編集済み takiru 2020年12月7日 8:58
    2020年12月7日 8:24
  • キャレットの長方形を求める部分を切り出しましょう。
    private static Rectangle GetCaretRectangle(Bitmap bmp)
    {
        var baseColor = SystemColors.Highlight;
        var pixelFormat = bmp.PixelFormat;
        int pixelSize = Image.GetPixelFormatSize(pixelFormat) / 8;
        BitmapData bmpData = bmp.LockBits(
            new Rectangle(0, 0, bmp.Width, bmp.Height),
            ImageLockMode.ReadWrite,
            pixelFormat);
    
        IntPtr ptr = bmpData.Scan0;
        byte[] pixels = new byte[bmpData.Stride * bmp.Height];
        Marshal.Copy(ptr, pixels, 0, pixels.Length);
    
        var rangeStartX = -1;
        var rangeEndX = -1;
        var rangeStartY = 4;
        var rangeEndY = bmpData.Height - 4;
        for (int x = 2; x < bmpData.Width - 30; x++)
        {
            int pos = rangeStartY * bmpData.Stride + x * pixelSize;
            if (pixels[pos] == baseColor.B && pixels[pos + 1] == baseColor.G && pixels[pos + 2] == baseColor.R)
            {
                if (rangeStartX == -1)
                {
                    rangeStartX = x;
                }
                else
                {
                    rangeEndX = x;
                }
            }
        }
        for (int y = rangeStartY; y > 0; y--)
        {
            int pos = y * bmpData.Stride + rangeStartX * pixelSize;
            if (pixels[pos] == baseColor.B && pixels[pos + 1] == baseColor.G && pixels[pos + 2] == baseColor.R)
            {
                rangeStartY = y;
            }
        }
        bmp.UnlockBits(bmpData);
        return Rectangle.FromLTRB(rangeStartX, rangeStartY, rangeEndX + 1, rangeEndY); 
    }
    
    WmPaint の部分はこうなります。
    private void WmPaint(ref Message m)
    {
        var cs = this.ClientSize;
        var dti = new DATETIMEPICKERINFO();
        var ret = SendMessage(this.Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, dti);
        var bsz = SystemInformation.Border3DSize;
        var canvas = new Rectangle(bsz.Width, bsz.Height, dti.rcButton.Left - bsz.Width, cs.Height - bsz.Height * 2);
    
        using (var bmpBack = CreateNativeBitmap())
        using (var bmpFore = CreateNagetiveBitmap(bmpBack))
        {
    
            BitBlt(bmpBack, BackColor, canvas, SRCAND);
            BitBlt(bmpFore, ForeColor, canvas, SRCAND);
    
            using (var bmp = new Bitmap(cs.Width, cs.Height))
            {
                using (var g = Graphics.FromImage(bmp))
                {
                    IntPtr hdc = g.GetHdc();
                    BitBlt(hdc, bmpBack, SRCCOPY);
                    BitBlt(hdc, bmpFore, canvas, canvas.Location, SRCPAINT);
    
                    // フォーカスがあるとき、キャレットの部分のみ転送
                    if (this.ContainsFocus)
                    {
                        using (var bmpOrg = CreateNativeBitmap())
                        {
                            Rectangle caret = GetCaretRectangle(bmpOrg);
                            BitBlt(hdc, bmpOrg, caret, caret.Location, SRCCOPY);
                        }
                    }
                    g.ReleaseHdc();
                }
    
                if (m.WParam == IntPtr.Zero)
                {
                    // コントロールに描画
                    var ps = new PAINTSTRUCT();
                    var controlHdc = BeginPaint(m.HWnd, ref ps);
                    using (var controlGraphics = Graphics.FromHdc(controlHdc))
                    {
                        controlGraphics.DrawImage(bmp, 0, 0);
                    }
                    EndPaint(m.HWnd, ref ps);
                }
                else
                {
                    // hdc に描画
                    using (var controlGraphics = Graphics.FromHdc(m.WParam))
                    {
                        controlGraphics.DrawImage(bmp, 0, 0);
                    }
                }
            }
        }
    }
    

    • 回答としてマーク takiru 2020年12月9日 0:28
    2020年12月7日 9:36
  • やりたいことを綺麗にまとめてくださってありがとうございます!

    多少は制約で諦めようかと思っていましたが、DPI変更やWindowsクラシックのテーマでも問題ないことを確認しました。

    あとはShowCheckBoxやShowUpDownがtrueの場合も対応できたら良いと思っています。
    これから、DATETIMEPICKERINFOの内容を確認してみようかと思っています。

    グラフィック制御が全く理解できていないため、コードが完成後、少しずつ実行して理解していく予定です。

    2020年12月8日 0:37
  • ShowCheckBox=trueの場合の加味は、canvasの取得を

                var canvas = new Rectangle(bsz.Width + (dti.rcCheck.Right != 0 ? dti.rcCheck.Right - 1 : 0),
                        bsz.Height, dti.rcButton.Left - bsz.Width - (dti.rcCheck.Right != 0 ? dti.rcCheck.Right - 1 : 0), cs.Height - bsz.Height * 2);

    にすることで対応できましたが、ShowUpDown=trueの場合は、dti.rcButtonが0しか返ってこず、
    もう、20ピクセル固定として考えて、幅をClientSize.Width - 20とせざるを得ませんでした。

                var x = bsz.Width + (dti.rcCheck.Right != 0 ? dti.rcCheck.Right - 1 : 0);
                var y = bsz.Height;
                var width = (dti.rcButton.Left != 0 ? dti.rcButton.Left : cs.Width - 20) - bsz.Width - (dti.rcCheck.Right != 0 ? dti.rcCheck.Right - 1 : 0);
                var height = cs.Height - bsz.Height * 2;
                var canvas = new Rectangle(x, y, width, height);

    ShowUpDown=trueの時に、正確な幅情報を得る方法はありますでしょうか?

    2020年12月8日 2:07
  • ShowUpDown = true の場合は DATETIMEPICKERINFO.hwndUD にアップダウンコントロールのウインドウハンドルが入ってくるようです。

            private Rectangle GetCanvasRectangle()
            {
                var dti = new DATETIMEPICKERINFO();
                var ret = SendMessage(this.Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, dti);
                var cs = this.ClientSize;
                var bsz = SystemInformation.Border3DSize;
                if (ShowUpDown)
                {
                    RECT rc = new RECT();
                    GetWindowRect(dti.hwndUD, out rc);
                    Point point = PointToClient(new Point(rc.Left, rc.Top));
                    return new Rectangle(bsz.Width, bsz.Height, point.X - bsz.Width, cs.Height - bsz.Height * 2);
                }
                else
                {
                    return new Rectangle(bsz.Width, bsz.Height, dti.rcButton.Left - bsz.Width, cs.Height - bsz.Height * 2);
                }
            }
    

    2020年12月8日 2:52
  • 度々ありがとうございます。

    希望する動作が完全にできました、ありがとうございます!
    一度コードを整理し、再掲しようかと思います。

    最後に1点だけご教示ください。
    下記のelse部分には、どういったタイミングで走行するのでしょうか?
    デザインに貼り付けた時、当該コントロールが継承されたコントロールを利用した時などを検証してみましたが、
    いつ走行するのか分かりませんでした。

    WM_PAINTの時、WPARAM、LPARAMともに利用されないようなので、このif()は不要と見なしても問題ないでしょうか?

                        if (m.WParam == IntPtr.Zero)
                        {
                            // コントロールに描画
                            var ps = new PAINTSTRUCT();
                            var controlHdc = BeginPaint(m.HWnd, ref ps);
                            using (var controlGraphics = Graphics.FromHdc(controlHdc))
                            {
                                controlGraphics.DrawImage(bmp, 0, 0);
                            }
                            EndPaint(m.HWnd, ref ps);
                        }
                        else
                        {
                            // hdc に描画
                            using (var controlGraphics = Graphics.FromHdc(m.WParam))
                            {
                                controlGraphics.DrawImage(bmp, 0, 0);
                            }
                        }

    2020年12月8日 4:20
  • 度々ご回答ありがとうございます。

    意図的にWPARAMに設定した時にそういう動作になる、ということだったんですね。
    確かに下記のコードだけが行われた時にそういう動作をすることを確認しました。

            protected override void WndProc(ref Message m)
            {
                if (m.Msg == WM_PAINT)
                {
                    m.WParam = this.FindForm().CreateGraphics().GetHdc();
                }
                base.WndProc(ref m);
            }
    そういう動作を意図的にしないと通らない、という点が、精通していないためにまたすぐ忘れてしまいそうです・・・。
    すごい意味がわかりました。ありがとうございました。

    2020年12月8日 9:28
  • 念のため。

    サンプルコードをいただいて、ご自身の作業成果を作ること自体は否定しませんが、それぞれのサンプルコードには著作権があります。
    それを下敷きにご自身で実装されたとしても、派生著作物となることがありますので、著作権者各位のライセンス条件を確認してくださいね。

    // MICROSOFT LIMITED PUBLIC LICENSE は最近見つけづらくなったなぁ…。
    // フォーラム使用条件という当時の文書はすぐに見当たらないので、現状、ライセンス条件を明示しないコードの扱いは不詳かなぁ…。

    2020年12月8日 12:40
    モデレータ
  • ご忠告ありがとうございます。

    内容もすべて理解しているわけではないので、自分でそれぞれ理解を行って整理した上で、望む状態で組み込みます。

    2020年12月9日 0:07
  • こちらのコードで望む動作をすべて実現することができました。ありがとうございました。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace Test
    {
        class DateTimePickerEx : DateTimePicker
        {
            [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
            public override Color ForeColor { get => base.ForeColor; set => base.ForeColor = value; }
    
            [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
            public override Color BackColor { get => base.BackColor; set => base.BackColor = value; }
    
            private const int WM_PAINT = 0x000F;
    
            private const int SRCPAINT = 0x00EE0086;
            private const int SRCAND = 0x8800C6;
            private const int NOTSRCCOPY = 0x330008;
            private const int SRCCOPY = 0xCC0020;
    
            [DllImport("gdi32.dll")]
            private static extern int BitBlt(IntPtr hdc, int x, int y, int cx, int cy, IntPtr hdcSrc, int x1, int y1, int rop);
    
            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct PAINTSTRUCT
            {
                public IntPtr hdc;
                public bool fErase;
                public RECT rcPaint;
                public bool fRestore;
                public bool fIncUpdate;
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] rgbReserved;
            }
    
            [DllImport("user32.dll")]
            private static extern IntPtr BeginPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
    
            [DllImport("user32.dll")]
            private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern bool DeleteDC(IntPtr hdc);
    
            [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
            private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
    
            [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool DeleteObject([In] IntPtr hgdiobj);
    
            [DllImport("user32.dll")]
            private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
    
            [StructLayout(LayoutKind.Sequential)]
            private class DATETIMEPICKERINFO
            {
                public int cbSize;
                public RECT rcCheck;
                public int stateCheck;
                public RECT rcButton;
                public int stateButton;
                public IntPtr hwndEdit;
                public IntPtr hwndUD;
                public IntPtr hwndDropDown;
    
                public DATETIMEPICKERINFO()
                {
                    cbSize = Marshal.SizeOf(this);
                }
            }
    
            private const int DTM_FIRST = 0x1000;
            private const int DTM_GETDATETIMEPICKERINFO = DTM_FIRST + 14;
    
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, DATETIMEPICKERINFO lParam);
    
            protected override void WndProc(ref Message m)
            {
                switch (m.Msg)
                {
                    case WM_PAINT:
                        WmPaint(ref m);
                        break;
                    default:
                        base.WndProc(ref m);
                        break;
                }
            }
    
            private static Bitmap CreateColorBitmap(Color color, Size size)
            {
                var bmp = new Bitmap(size.Width, size.Height);
                using (var g = Graphics.FromImage(bmp))
                {
                    g.Clear(color);
                }
                return bmp;
            }
    
            private static void BitBlt(IntPtr hdc, Color color, Rectangle rectangle, int dwRop)
            {
                using (Bitmap bmp = CreateColorBitmap(color, rectangle.Size))
                {
                    BitBlt(hdc, bmp, rectangle, dwRop);
                }
            }
    
            private static void BitBlt(Bitmap bmp, Color color, Rectangle rectangle, int dwRop)
            {
                using (var g = Graphics.FromImage(bmp))
                {
                    IntPtr hdc = g.GetHdc();
                    BitBlt(hdc, bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), SRCCOPY);
                    BitBlt(hdc, color, rectangle, dwRop);
                    g.ReleaseHdc();
                }
            }
    
            private static void BitBlt(IntPtr hdc, Bitmap bmp, int dwRop)
            {
                BitBlt(hdc, bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), dwRop);
            }
    
            private static void BitBlt(IntPtr hdc, Bitmap bmp, Rectangle rectangle, int dwRop)
            {
                BitBlt(hdc, bmp, rectangle, new Point(0, 0), dwRop);
            }
    
            private static void BitBlt(IntPtr hdc, Bitmap bmp, Rectangle rectangle, Point point, int dwRop)
            {
                IntPtr hdcSrc = CreateCompatibleDC(hdc);
                IntPtr hBitmap = bmp.GetHbitmap();
                IntPtr hbmpOld = SelectObject(hdcSrc, hBitmap);
    
                BitBlt(hdc,
                    rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height,
                    hdcSrc, point.X, point.Y, dwRop);
    
                SelectObject(hdcSrc, hbmpOld);
                DeleteObject(hBitmap);
                DeleteDC(hdcSrc);
            }
    
            private Bitmap CreateNativeBitmap()
            {
                var cs = this.ClientSize;
                var bmp = new Bitmap(cs.Width, cs.Height);
                using (var g = Graphics.FromImage(bmp))
                {
                    var hdc = g.GetHdc();
                    var pm = Message.Create(this.Handle, WM_PAINT, hdc, IntPtr.Zero);
                    base.DefWndProc(ref pm);
                    g.ReleaseHdc();
                }
                return bmp;
            }
    
            private static Bitmap CreateNegativeBitmap(Bitmap bmp)
            {
                var bmpDest = new Bitmap(bmp.Width, bmp.Height);
                using (var g = Graphics.FromImage(bmpDest))
                {
                    var hdc = g.GetHdc();
                    BitBlt(hdc, bmp, NOTSRCCOPY);
                    g.ReleaseHdc();
                }
                return bmpDest;
            }
    
            /// <summary>
            /// 選択領域の座標を求める。
            /// </summary>
            /// <param name="bmp">Bitmap オブジェクト。</param>
            /// <param name="canvas">Rectangle オブジェクト。</param>
            /// <returns>選択領域座標の Rectangle オブジェクト。</returns>
            private static Rectangle GetCaretRectangle(Bitmap bmp, Rectangle canvas)
            {
                var baseColor = SystemColors.Highlight;
                int pixelSize = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
                BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
                    ImageLockMode.ReadOnly, bmp.PixelFormat);
    
                byte[] pixels = new byte[bmpData.Stride * bmp.Height];
                Marshal.Copy(bmpData.Scan0, pixels, 0, pixels.Length);
    
                var rangeStartX = -1;
                var rangeEndX = -1;
                var rangeStartY = canvas.Top + 2;   // Yの開始座標を、とりあえず4ピクセル目とする
                var rangeEndY = bmpData.Height - 4; // Yの終了座標は、どの環境でも全体レイアウトの高さ-4
    
                // X方向へ選択領域座標を取得
                for (int x = canvas.Left; x <= canvas.Left + canvas.Width; x++)
                {
                    int pos = rangeStartY * bmpData.Stride + x * pixelSize;
                    if (!(pixels[pos] == baseColor.B && pixels[pos + 1] == baseColor.G && pixels[pos + 2] == baseColor.R))
                    {
                        continue;
                    }
                    if (rangeStartX == -1)
                    {
                        rangeStartX = x;
                    }
                    else
                    {
                        rangeEndX = x;
                    }
                }
                // Y開始座標が3ピクセル目の場合があるので、開始座標を求め直す
                for (int y = rangeStartY; y >= canvas.Top; y--)
                {
                    int pos = y * bmpData.Stride + rangeStartX * pixelSize;
                    if (!(pixels[pos] == baseColor.B && pixels[pos + 1] == baseColor.G && pixels[pos + 2] == baseColor.R))
                    {
                        continue;
                    }
                    rangeStartY = y;
                }
                bmp.UnlockBits(bmpData);
                return Rectangle.FromLTRB(rangeStartX, rangeStartY, rangeEndX + 1, rangeEndY);
            }
    
            /// <summary>
            /// 入力エリア領域を求める。
            /// </summary>
            /// <returns>Rectangle オブジェクト。</returns>
            private Rectangle GetCanvasRectangle()
            {
                var cs = this.ClientSize;
                var bsz = SystemInformation.Border3DSize;
    
                var dti = new DATETIMEPICKERINFO();
                SendMessage(this.Handle, DTM_GETDATETIMEPICKERINFO, IntPtr.Zero, dti);
                var x = bsz.Width + (dti.rcCheck.Right != 0 ? dti.rcCheck.Right - 1 : 0);
                var y = bsz.Height;
                var height = cs.Height - bsz.Height * 2;
    
                if (ShowUpDown)
                {
                    RECT rc;
                    GetWindowRect(dti.hwndUD, out rc);
                    Point point = PointToClient(new Point(rc.Left, rc.Top));
                    var width = point.X - bsz.Width - (dti.rcCheck.Right != 0 ? dti.rcCheck.Right - 1 : 0);
                    return new Rectangle(x, y, width, height);
                }
                else
                {
                    var width = dti.rcButton.Left - bsz.Width - (dti.rcCheck.Right != 0 ? dti.rcCheck.Right - 1 : 0);
                    return new Rectangle(x, y, width, height);
                }
            }
    
            /// <summary>
            /// 背景色、文字色、キャレット範囲をビットブロック転送する。
            /// </summary>
            /// <param name="bmp">Bitmap オブジェクト。</param>
            private void BitBltGraphics(Bitmap bmp)
            {
                using (var g = Graphics.FromImage(bmp))
                using (var bmpBack = CreateNativeBitmap())
                using (var bmpFore = CreateNegativeBitmap(bmpBack))
                {
                    var canvas = GetCanvasRectangle();
    
                    BitBlt(bmpBack, BackColor, canvas, SRCAND);
                    BitBlt(bmpFore, ForeColor, canvas, SRCAND);
    
                    IntPtr hdc = g.GetHdc();
                    BitBlt(hdc, bmpBack, SRCCOPY);
                    BitBlt(hdc, bmpFore, canvas, canvas.Location, SRCPAINT);
    
                    // フォーカスがあるとき、キャレットの部分のみ転送
                    if (this.ContainsFocus)
                    {
                        using (var bmpOrg = CreateNativeBitmap())
                        {
                            Rectangle caret = GetCaretRectangle(bmpOrg, canvas);
                            BitBlt(hdc, bmpOrg, caret, caret.Location, SRCCOPY);
                        }
                    }
                    g.ReleaseHdc();
                }
            }
    
            private void WmPaint(ref Message m)
            {
                var cs = this.ClientSize;
                using (var bmp = new Bitmap(cs.Width, cs.Height))
                {
                    BitBltGraphics(bmp);
    
                    if (m.WParam == IntPtr.Zero)
                    {
                        // コントロールに描画
                        var ps = new PAINTSTRUCT();
                        var controlHdc = BeginPaint(m.HWnd, ref ps);
                        using (var controlGraphics = Graphics.FromHdc(controlHdc))
                        {
                            controlGraphics.DrawImage(bmp, 0, 0);
                        }
                        EndPaint(m.HWnd, ref ps);
                    } else
                    {
                        // hdc に描画(WM_PAINTの動作は、意図的にWParamにHDCを設定した時は、そのHDCに描画する仕様)
                        using (var controlGraphics = Graphics.FromHdc(m.WParam))
                        {
                            controlGraphics.DrawImage(bmp, 0, 0);
                        }
                    }
                }
            }
        }
    }

    Windows7

    Windows7 クラシックテーマ

    Windows10


    • 編集済み takiru 2020年12月9日 4:16
    2020年12月9日 0:28