File: System\Windows\Forms\Design\PbrsForward.cs
Web Access
Project: src\src\System.Windows.Forms.Design\src\System.Windows.Forms.Design.csproj (System.Windows.Forms.Design)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel.Design;
 
namespace System.Windows.Forms.Design;
 
internal partial class PbrsForward : IWindowTarget
{
    private readonly Control _target;
    private readonly IWindowTarget _oldTarget;
 
    // We save the last key down so we can recreate the last message if we need to activate
    // the properties window.
    private Message _lastKeyDown;
    private List<BufferedKey>? _bufferedChars;
 
    private const int WM_PRIVATE_POSTCHAR = (int)PInvokeCore.WM_USER + 0x1598;
    private bool _postCharMessage;
 
    private IMenuCommandService? _menuCommandSvc;
 
    private readonly IServiceProvider _sp;
 
    private bool _ignoreMessages;
 
    public PbrsForward(Control target, IServiceProvider sp)
    {
        _target = target;
        _oldTarget = target.WindowTarget;
        _sp = sp;
        target.WindowTarget = this;
    }
 
    private IMenuCommandService? MenuCommandService
    {
        get
        {
            if (_menuCommandSvc is null && _sp is not null)
            {
                _menuCommandSvc = _sp.GetService<IMenuCommandService>();
            }
 
            return _menuCommandSvc;
        }
    }
 
    private ISupportInSituService? InSituSupportService
        => _sp.GetService<ISupportInSituService>();
 
    public void Dispose() => _target.WindowTarget = _oldTarget;
 
    /// <summary>
    ///  Called when the window handle of the control has changed.
    /// </summary>
    void IWindowTarget.OnHandleChange(IntPtr newHandle)
    {
    }
 
    /// <summary>
    ///  Called to do control-specific processing for this window.
    /// </summary>
    void IWindowTarget.OnMessage(ref Message m)
    {
        // Get the Designer for the currently selected item on the Designer...
        // SET STATE ..
        _ignoreMessages = false;
 
        // Here lets query for the ISupportInSituService.
        // If we find the service then ask if it has a designer which is interested
        // in getting the keychars by querying the IgnoreMessages.
        if (m.Msg is >= ((int)PInvokeCore.WM_KEYFIRST)
            and <= ((int)PInvokeCore.WM_KEYLAST) or >= ((int)PInvokeCore.WM_IME_STARTCOMPOSITION)
            and <= ((int)PInvokeCore.WM_IME_COMPOSITION))
        {
            if (InSituSupportService is ISupportInSituService supportInSituService)
            {
                _ignoreMessages = supportInSituService.IgnoreMessages;
            }
        }
 
        switch (m.Msg)
        {
            case WM_PRIVATE_POSTCHAR:
 
                if (_bufferedChars is null)
                {
                    return;
                }
 
                // recreate the keystroke to the newly activated window
                HWND hwnd;
                hwnd = !_ignoreMessages || InSituSupportService is not ISupportInSituService supportInSituService
                    ? PInvoke.GetFocus()
                    : (HWND)supportInSituService.GetEditWindow();
 
                if (hwnd != m.HWnd)
                {
                    foreach (BufferedKey bk in _bufferedChars)
                    {
                        if (bk.KeyChar.MsgInternal == PInvokeCore.WM_CHAR)
                        {
                            if (bk.KeyDown.MsgInternal != 0)
                            {
                                PInvokeCore.SendMessage(hwnd, PInvokeCore.WM_KEYDOWN, bk.KeyDown.WParamInternal, bk.KeyDown.LParamInternal);
                            }
 
                            PInvokeCore.SendMessage(hwnd, PInvokeCore.WM_CHAR, bk.KeyChar.WParamInternal, bk.KeyChar.LParamInternal);
                            if (bk.KeyUp.MsgInternal != 0)
                            {
                                PInvokeCore.SendMessage(hwnd, PInvokeCore.WM_KEYUP, bk.KeyUp.WParamInternal, bk.KeyUp.LParamInternal);
                            }
                        }
                        else
                        {
                            PInvokeCore.SendMessage(hwnd, bk.KeyChar.MsgInternal, bk.KeyChar.WParamInternal, bk.KeyChar.LParamInternal);
                        }
                    }
                }
 
                _bufferedChars.Clear();
                return;
 
            case (int)PInvokeCore.WM_KEYDOWN:
                _lastKeyDown = m;
                break;
 
            case (int)PInvokeCore.WM_IME_ENDCOMPOSITION:
            case (int)PInvokeCore.WM_KEYUP:
                _lastKeyDown.Msg = 0;
                break;
 
            case (int)PInvokeCore.WM_CHAR:
            case (int)PInvokeCore.WM_IME_STARTCOMPOSITION:
            case (int)PInvokeCore.WM_IME_COMPOSITION:
                if ((Control.ModifierKeys & (Keys.Control | Keys.Alt)) != 0)
                {
                    break;
                }
 
                _bufferedChars ??= [];
                _bufferedChars.Add(new BufferedKey(_lastKeyDown, m, _lastKeyDown));
 
                if (!_ignoreMessages && MenuCommandService is not null)
                {
                    // throw the properties window command, we will redo the keystroke when we actually
                    // lose focus
                    _postCharMessage = true;
                    MenuCommandService.GlobalInvoke(StandardCommands.PropertiesWindow);
                }
                else if (_ignoreMessages && m.Msg != (int)PInvokeCore.WM_IME_COMPOSITION)
                {
                    if (InSituSupportService is ISupportInSituService anotherSupportInSituService)
                    {
                        _postCharMessage = true;
                        anotherSupportInSituService.HandleKeyChar();
                    }
                }
 
                if (_postCharMessage)
                {
                    // If copy of message has been buffered for forwarding, eat the original now
                    return;
                }
 
                break;
 
            case (int)PInvokeCore.WM_KILLFOCUS:
                if (_postCharMessage)
                {
                    // Now that we've actually lost focus, post this message to the queue. This allows any activity
                    // that's in the queue to settle down before our characters are posted to the queue.
                    //
                    // We post because we need to allow the focus to actually happen before we send our strokes so we
                    // know where to send them.
                    //
                    // We can't use the wParam here because it may not be the actual window that needs to pick up
                    // the strokes.
                    PInvokeCore.PostMessage(_target, (MessageId)WM_PRIVATE_POSTCHAR);
                    _postCharMessage = false;
                }
 
                break;
        }
 
        _oldTarget?.OnMessage(ref m);
    }
}