0

Updated to reflect a more functional description of my request.

Functionally I need a read only ComboBox that does not directly inherit from the System.Windows.Forms.ComboBox class. I need the text box portion to be read only, so the user can copy out text that was entered into the box but not change it. And this is not a case of using DropDown vs DropDownList mode.

Using this article and some experimentation, I have the text box portion taken care of but still need to prevent changes from the drop down component due to technical reasons listed below. I still need one of the following:

  1. Disable the drop down button on a ComboBox - to prevent it from opening
  2. Prevent the drop down from opening in a ComboBox when clicked
  3. Prevent the selection of an item from the drop down from changing the selection in the control (possibly by intercepting and ignoring the clicked item notification message)

I'm using the KryptonToolkit for visual prettiness in my application. For the KryptonComboBox control, it has an internal, extended System.Windows.Forms.ComboBox class that intercepts the draw calls. It exposes access to the ComboBox object, so I can get access to it's Handle.

I'm using the A Complete Read Only ComboBox code as a base to create a read only capable version of the KryptonComboBox. Instead of using the KryptonComboBox.Handle in the SendMessage() calls, you have to use the KryptonComboBox.ComboBox.Handle to get the actual handle to the Windows control. By doing that I'm able to set the read only mode on the text box part of the ComboBox correctly.

BUT! Since the base class of the extended control is KryptonComboBox and not ComboBox, when the WndProc() is called it's for messages of the KryptonComboBox. So I am unable to prevent the control from acting (changing the selection) on the clicked drop down item when it is in read only mode.

How do I intercept (and potentially ignore) WM_COMMAND messages from an existing ComboBox control. Specifically I want to have a message 273 chain ignored under certain circumstances. Is there a way to functionally do the same thing to an "external" ComboBox control that I could from an inherited control? Since I have the Handle for the ComboBox, could I use something like SetWindowsHookEx() to intercept the messages?

CuppM
  • 1,668
  • 4
  • 19
  • 30
  • So you don't want the user to be able to select a new value from the dropdown? Why use a combo at all then, just use a read-only edit control. – Jonathan Potter Oct 08 '14 at 20:28
  • By intercepting WM_COMMAND, you're diving very deep within the beast to come across WIN32 API. I wouldn't play this game unless you really know what you're doing. Instead, there's a property `DropDownStyle` which prevent text input in the `TextBox` part of your `ComboBox`. If what you want is to prevent the user from changing selection by typing, try to intercept the event through the `KeyPress`, `KeyDown`, `KeyUp` or any `Key*` event before playing this deep. Or yet simpler, use another control, since the `ComboBox` is actually used to allow the user to choose among multiple choices. – Will Marcouiller Oct 08 '14 at 20:29
  • Because the control will toggle in/out of read only mode as the user manipulates the object(s). The application is a front end to a database with specific rules for what can be edited and when. And when the user is configuring the object I use the drop down to provide existing values for them to choose. And I want it read only instead of disabled so they can select and copy out the values used. – CuppM Oct 08 '14 at 20:31
  • Actually, I guess it would be more precise to describe your functional requirement other than this technical stuff you threw up. ;) Please edit your question so that it expresses what are your functional requirements, what you want it to do and when and why. – Will Marcouiller Oct 08 '14 at 20:34
  • @Will: I'm cool with plumbing into the beast, done it before (made a dynamically populating auto complete source). And it's not that I just want to limit their choices to predetermined ones, I want to prevent them from changing while retaining their ability to select and copy existing values. It's probably a rare case that this functionality is applicable, but when you need it... – CuppM Oct 08 '14 at 20:36
  • If I understand correctly, the user needs to be able to copy the value from the `TextBox` part of your `ComboBox`, while not necessarily send the "Drop Down" message to the control so that one may select and copy the value, right? Why not implement a [CTRL]+[C] and check when the key combination is activated, then copy the value to the clipboard so that the user may have the desired value to paste elsewhere. How about it, does it sound fair? – Will Marcouiller Oct 08 '14 at 20:53
  • @Will: What about Rightclick -> Copy? What about [Ctrl]+[Ins]? Fiddling with keyboard input is **always** the wrong solution, no matter what the problem is. – IInspectable Oct 09 '14 at 09:46
  • @Will: Added an updated intro that's less technical based on your suggestion. Would the control respond to keyboard commands or right click context menus while disabled? I suppose if nothing else works, those might be acceptable. But that style just breaks too much from the rest of the controls. When you see a `TextBox` control in read only mode right next to a disabled `ComboBox`, it's not a coherent look. – CuppM Oct 09 '14 at 13:19

1 Answers1

0

So I found a solution that works for "plugging into" the command messages for the control and being able to interrupt them. It involves using the System.Windows.Forms.NativeWindow class to create a listener to the ComboBox which is (from my understanding) called subclassing.

Listener Class:

private class ComboBoxListener : NativeWindow, IDisposable {
    private ComboBox m_oComboBox;

    private EventHandler m_oComboBox_HandleCreated;
    private EventHandler m_oComboBox_HandleDestroyed;

    public ComboBoxListener(ComboBox oComboBox) {
        // Save the combobox and assign the handle if it's already created
        m_oComboBox = oComboBox;
        if (m_oComboBox.IsHandleCreated) {
            AssignHandle(m_oComboBox.Handle);
        }

        // Subscribe to the handle events of the combobox
        m_oComboBox.HandleCreated += m_oComboBox_HandleCreated = new EventHandler(ComboBox_HandleCreated);
        m_oComboBox.HandleDestroyed += m_oComboBox_HandleDestroyed = new EventHandler(ComboBox_HandleDestroyed);
    }

    ~ComboBoxListener() {
        Dispose(false);
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool bDisposing) {
        if (bDisposing && (m_oComboBox != null) && !m_oComboBox.IsDisposed) {
            // Unsubscribe from the combo box
            m_oComboBox.HandleCreated -= m_oComboBox_HandleCreated;
            m_oComboBox.HandleDestroyed -= m_oComboBox_HandleDestroyed;
        }
        m_oComboBox = null;

        // If a handle is currently assigned, release it
        if (this.Handle != IntPtr.Zero) {
            ReleaseHandle();
        }
    }

    private void ComboBox_HandleCreated(object sender, EventArgs e) {
        AssignHandle(m_oComboBox.Handle);
    }

    private void ComboBox_HandleDestroyed(object sender, EventArgs e) {
        ReleaseHandle();
    }

    protected override void WndProc(ref Message m) {
        // Do checks for whether to ignore the message
        // If ignoring, just call return and skip the base.WndProc(ref m) call
        base.WndProc(ref m);
    }
}

Then in the constructor for the deriving class from KryptonComboBox I added:

m_oComboBoxListener = new ComboBoxListener(this);

And added a cleanup in the overridden Dispose(bool disposing):

// Dispose the listener
if (disposing && (m_oComboBoxListener != null)) {
    m_oComboBoxListener.Dispose();
}
m_oComboBoxListener = null;
CuppM
  • 1,668
  • 4
  • 19
  • 30