Как нарисовать пользовательскую кнопку в заголовке окна с помощью Windows Forms?

Как нарисовать пользовательскую кнопку рядом с кнопками свертывания, разворачивания и закрытия в строке заголовка формы?

Я знаю, что вам нужно использовать вызовы Win32 API и переопределить процедуру WndProc, но мне не удалось найти решение, которое работает правильно.

Кто-нибудь знает как это сделать? Точнее, знает ли кто-нибудь способ сделать это, работающий в Vista?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
0
29 690
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Рисование кажется легким делом, с этим можно справиться:

[Обновлено: код удален, см. Мой другой ответ]

Настоящая проблема заключается в изменении состояния и обнаружении щелчков по кнопке ... для этого вам нужно подключиться к глобальному обработчику сообщений для программы, .NET, похоже, скрывает события мыши для формы, а не в фактическом контейнере. области (т. е. мышь перемещается и щелкает по строке заголовка). Я ищу информацию об этом, нашел это сейчас, я работаю над этим, не должно быть слишком сложно ... Если мы сможем выяснить, что эти сообщения на самом деле передают.

вы можете определить «не работает»? Это не распознавание сообщений, это не рисование? Он даже не компилируется?

Matthew Scharley 20.09.2008 09:47

Я подозреваю, что Vista может обрабатывать оконные ручки по-другому, и / или Aero мешает. Я знаю, что у меня были проблемы с отображением его в XP, потому что я использовал неправильный дескриптор для рисования.

Matthew Scharley 20.09.2008 09:57
Ответ принят как подходящий

Следующее будет работать в XP, у меня нет машины с Vista, чтобы проверить это, но я думаю, что ваши проблемы каким-то образом связаны с неправильным hWnd. Во всяком случае, с плохо прокомментированным кодом.

// The state of our little button
ButtonState _buttState = ButtonState.Normal;
Rectangle _buttPosition = new Rectangle();

[DllImport("user32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetWindowRect(IntPtr hWnd, 
                                        ref Rectangle lpRect);
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
protected override void WndProc(ref Message m)
{
    int x, y;
    Rectangle windowRect = new Rectangle();
    GetWindowRect(m.HWnd, ref windowRect);

    switch (m.Msg)
    {
        // WM_NCPAINT
        case 0x85:
        // WM_PAINT
        case 0x0A:
            base.WndProc(ref m);

            DrawButton(m.HWnd);

            m.Result = IntPtr.Zero;

            break;

        // WM_ACTIVATE
        case 0x86:
            base.WndProc(ref m);
            DrawButton(m.HWnd);

            break;

        // WM_NCMOUSEMOVE
        case 0xA0:
            // Extract the least significant 16 bits
            x = ((int)m.LParam << 16) >> 16;
            // Extract the most significant 16 bits
            y = (int)m.LParam >> 16;

            x -= windowRect.Left;
            y -= windowRect.Top;

            base.WndProc(ref m);

            if (!_buttPosition.Contains(new Point(x, y)) && 
                _buttState == ButtonState.Pushed)
            {
                _buttState = ButtonState.Normal;
                DrawButton(m.HWnd);
            }

            break;

        // WM_NCLBUTTONDOWN
        case 0xA1:
            // Extract the least significant 16 bits
            x = ((int)m.LParam << 16) >> 16;
            // Extract the most significant 16 bits
            y = (int)m.LParam >> 16;

            x -= windowRect.Left;
            y -= windowRect.Top;

            if (_buttPosition.Contains(new Point(x, y)))
            {
                _buttState = ButtonState.Pushed;
                DrawButton(m.HWnd);
            }
            else
                base.WndProc(ref m);

            break;

        // WM_NCLBUTTONUP
        case 0xA2:
            // Extract the least significant 16 bits
            x = ((int)m.LParam << 16) >> 16;
            // Extract the most significant 16 bits
            y = (int)m.LParam >> 16;

            x -= windowRect.Left;
            y -= windowRect.Top;

            if (_buttPosition.Contains(new Point(x, y)) &&
                _buttState == ButtonState.Pushed)
            {
                _buttState = ButtonState.Normal;
                // [[TODO]]: Fire a click event for your button 
                //           however you want to do it.
                DrawButton(m.HWnd);
            }
            else
                base.WndProc(ref m);

            break;

        // WM_NCHITTEST
        case 0x84:
            // Extract the least significant 16 bits
            x = ((int)m.LParam << 16) >> 16;
            // Extract the most significant 16 bits
            y = (int)m.LParam >> 16;

            x -= windowRect.Left;
            y -= windowRect.Top;

            if (_buttPosition.Contains(new Point(x, y)))
                m.Result = (IntPtr)18; // HTBORDER
            else
                base.WndProc(ref m);

            break;

        default:
            base.WndProc(ref m);
            break;
    }
}

private void DrawButton(IntPtr hwnd)
{
    IntPtr hDC = GetWindowDC(hwnd);
    int x, y;

    using (Graphics g = Graphics.FromHdc(hDC))
    {
        // Work out size and positioning
        int CaptionHeight = Bounds.Height - ClientRectangle.Height;
        Size ButtonSize = SystemInformation.CaptionButtonSize;
        x = Bounds.Width - 4 * ButtonSize.Width;
        y = (CaptionHeight - ButtonSize.Height) / 2;
        _buttPosition.Location = new Point(x, y);

        // Work out color
        Brush color;
        if (_buttState == ButtonState.Pushed)
            color = Brushes.LightGreen;
        else
            color = Brushes.Red;

        // Draw our "button"
        g.FillRectangle(color, x, y, ButtonSize.Width, ButtonSize.Height);
    }

    ReleaseDC(hwnd, hDC);
}

private void Form1_Load(object sender, EventArgs e)
{
    _buttPosition.Size = SystemInformation.CaptionButtonSize;
}

Это все еще не отображается графически в Vista. Все равно, спасибо за помощь.

Chris Pietschmann 20.09.2008 21:32

Красный прямоугольник, который отображается под заголовком (внутри рамки окна) в Windows 8 и более поздних версиях (включая предварительный просмотр технологий Win10 и инсайдеров). Я не ожидал, что это сработает, но подумал, что все равно прокомментирую, чтобы вы знали.

NDEIGU 21.05.2015 00:16

Я знаю, что прошло много времени с момента последнего ответа, но это действительно помогло мне в последнее время, и мне нравится обновлять код, предоставленный Крисом, с моими комментариями и изменениями. Эта версия отлично работает на Win XP и Win 2003. На Win 2008 есть небольшая ошибка, которую я не смог определить при изменении размера окон. Работает и в Vista (без Aero), но обратите внимание, что кнопки в строке заголовка не квадратные, и это следует учитывать при размерах кнопок.

 switch (m.Msg)
            {
                // WM_NCPAINT / WM_PAINT        
                case 0x85:
                case 0x0A:
                    //Call base method
                    base.WndProc(ref m);
                    //we have 3 buttons in the corner of the window. So first's new button left coord is offseted by 4 widths
                    int crt = 4;
                    //navigate trough all titlebar buttons on the form
                    foreach (TitleBarImageButton crtBtn in titleBarButtons.Values)
                    {
                        //Calculate button coordinates
                        p.X = (Bounds.Width - crt * crtBtn.Size.Width);
                        p.Y = (Bounds.Height - ClientRectangle.Height - crtBtn.Size.Height) / 2;
                        //Initialize button and draw
                        crtBtn.Location = p;
                        crtBtn.ButtonState = ImageButtonState.NORMAL;
                        crtBtn.DrawButton(m.HWnd);
                        //increment button left coord location offset
                        crt++;
                    }
                    m.Result = IntPtr.Zero;
                    break;
                // WM_ACTIVATE      
                case 0x86:
                    //Call base method
                    base.WndProc(ref m);
                    //Draw each button
                    foreach (TitleBarImageButton crtBtn in titleBarButtons.Values)
                    {
                        crtBtn.ButtonState = ImageButtonState.NORMAL;
                        crtBtn.DrawButton(m.HWnd);
                    }
                    break;
                // WM_NCMOUSEMOVE        
                case 0xA0:
                    //Get current mouse position
                    p.X = ((int)m.LParam << 16) >> 16;// Extract the least significant 16 bits            
                    p.Y = (int)m.LParam >> 16;        // Extract the most significant 16 bits          
                    p.X -= windowRect.Left;
                    p.Y -= windowRect.Top;

                    //Call base method
                    base.WndProc(ref m);

                    ImageButtonState newButtonState;
                    foreach (TitleBarImageButton crtBtn in titleBarButtons.Values)
                    {
                        if (crtBtn.HitTest(p))
                        {//mouse is over the current button
                            if (crtBtn.MouseButtonState == MouseButtonState.PRESSED)
                                //button is pressed - set pressed state
                                newButtonState = ImageButtonState.PRESSED;
                            else
                                //button not pressed - set hoover state
                                newButtonState = ImageButtonState.HOOVER;
                        }
                        else
                        {
                            //mouse not over the current button - set normal state
                            newButtonState = ImageButtonState.NORMAL;
                        }

                        //if button state not modified, do not repaint it.
                        if (newButtonState != crtBtn.ButtonState)
                        {
                            crtBtn.ButtonState = newButtonState;
                            crtBtn.DrawButton(m.HWnd);
                        }
                    }
                    break;
                // WM_NCLBUTTONDOWN     
                case 0xA1:
                    //Get current mouse position
                    p.X = ((int)m.LParam << 16) >> 16;// Extract the least significant 16 bits
                    p.Y = (int)m.LParam >> 16;        // Extract the most significant 16 bits      
                    p.X -= windowRect.Left;
                    p.Y -= windowRect.Top;

                    //Call base method
                    base.WndProc(ref m);

                    foreach (TitleBarImageButton crtBtn in titleBarButtons.Values)
                    {
                        if (crtBtn.HitTest(p))
                        {
                            crtBtn.MouseButtonState = MouseButtonState.PRESSED;
                            crtBtn.ButtonState = ImageButtonState.PRESSED;
                            crtBtn.DrawButton(m.HWnd);
                        }
                    }
                    break;
                // WM_NCLBUTTONUP   
                case 0xA2:
                case 0x202:
                    //Get current mouse position
                    p.X = ((int)m.LParam << 16) >> 16;// Extract the least significant 16 bits   
                    p.Y = (int)m.LParam >> 16;        // Extract the most significant 16 bits 
                    p.X -= windowRect.Left;
                    p.Y -= windowRect.Top;

                    //Call base method
                    base.WndProc(ref m);
                    foreach (TitleBarImageButton crtBtn in titleBarButtons.Values)
                    {
                        //if button is press
                        if (crtBtn.ButtonState == ImageButtonState.PRESSED)
                        {
                            //Rasie button's click event
                            crtBtn.OnClick(EventArgs.Empty);

                            if (crtBtn.HitTest(p))
                                crtBtn.ButtonState = ImageButtonState.HOOVER;
                            else
                                crtBtn.ButtonState = ImageButtonState.NORMAL;
                        }

                        crtBtn.MouseButtonState = MouseButtonState.NOTPESSED;
                        crtBtn.DrawButton(m.HWnd);
                    }
                    break;
                // WM_NCHITTEST    
                case 0x84:
                    //Get current mouse position
                    p.X = ((int)m.LParam << 16) >> 16;// Extract the least significant 16 bits
                    p.Y = (int)m.LParam >> 16;        // Extract the most significant 16 bits
                    p.X -= windowRect.Left;
                    p.Y -= windowRect.Top;

                    bool isAnyButtonHit = false;
                    foreach (TitleBarImageButton crtBtn in titleBarButtons.Values)
                    {
                        //if mouse is over the button, or mouse is pressed 
                        //(do not process messages when mouse was pressed on a button)
                        if (crtBtn.HitTest(p) || crtBtn.MouseButtonState == MouseButtonState.PRESSED)
                        {
                            //return 18 (do not process further)
                            m.Result = (IntPtr)18;
                            //we have a hit
                            isAnyButtonHit = true;
                            //return 
                            break;
                        }
                        else
                        {//mouse is not pressed and not over the button, redraw button if needed  
                            if (crtBtn.ButtonState != ImageButtonState.NORMAL)
                            {
                                crtBtn.ButtonState = ImageButtonState.NORMAL;
                                crtBtn.DrawButton(m.HWnd);
                            }
                        }
                    }
                    //if we have a hit, do not process further
                    if (!isAnyButtonHit)
                        //Call base method
                        base.WndProc(ref m);
                    break;
                default:
                    //Call base method
                    base.WndProc(ref m);
                    //Console.WriteLine(m.Msg + "(0x" + m.Msg.ToString("x") + ")");
                    break;
            }

Код демонстрирует сообщения, которые нужно обработать, и способы их обработки. В коде используется набор настраиваемых объектов TitleBarButton. Этот класс слишком велик, чтобы включать его здесь, но я могу предоставить его, если необходимо, вместе с примером.

Мне это очень интересно. Не могли бы вы привести пример для демонстрации? Спасибо.

Gnought 18.05.2010 12:25

Комментарии, содержащие «WM_PAINT» и т. д., Некорректно соотносятся с числовыми значениями в случаях переключения. Например, 0x0A - это WM_ENABLE, а не WM_PAINT. И есть еще немало других. Я предполагаю, что числовые значения правильные, а комментарии - это то, что нужно исправить, но я не хочу предлагать редактирование, не будучи уверенным.

Bryce Wagner 03.12.2014 21:23

Другие вопросы по теме