catch tabpage change
-
Hi. How can i prevent a user from changing tabpage without removing tabpages and without the flicker that occurs if i in the selectedIndexChanged event set it back to the original tabpage? It seems that all events a raised AFTER the tabpage changed.... even the various mouse events...
-
Hi. How can i prevent a user from changing tabpage without removing tabpages and without the flicker that occurs if i in the selectedIndexChanged event set it back to the original tabpage? It seems that all events a raised AFTER the tabpage changed.... even the various mouse events...
Yes, of course it's called after. It is
SelectedIndexChang**ed**
afterall. In order to prevent this, you're going to have to derive fromTabControl
and overrideWndProc
and handle theTCN_SELCHANGING
(-552). Returntrue
to prevent the change:private const int TCN_SELCHANGING = -552;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == TCN_SELCHANGING)
{
// Uses made-up variable below - use whatever condition you need.
if (!allowChange) m.Result = new IntPtr(1); // TRUE
}
}Microsoft MVP, Visual C# My Articles
-
Yes, of course it's called after. It is
SelectedIndexChang**ed**
afterall. In order to prevent this, you're going to have to derive fromTabControl
and overrideWndProc
and handle theTCN_SELCHANGING
(-552). Returntrue
to prevent the change:private const int TCN_SELCHANGING = -552;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == TCN_SELCHANGING)
{
// Uses made-up variable below - use whatever condition you need.
if (!allowChange) m.Result = new IntPtr(1); // TRUE
}
}Microsoft MVP, Visual C# My Articles
:confused:Forgive me, but I'm a little confused. Heath Stewart wrote: private const int TCN_SELCHANGING = -552; I'm assuming that's from CommCtrl.h :
#define TCN_FIRST (0U-550U) // tab control
...
#define TCN_SELCHANGING (TCN_FIRST - 2)But the MSDN library has the following definition for WindowProc:
LRESULT CALLBACK WindowProc( HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);Which leads me to believe that uMsg will never be -552. To test it, I derived a control from
TabControl
and overrodeWndProc
as such:protected override void WndProc(ref Message m) {
Console.WriteLine(m.Msg);
base.WndProc (ref m);
}I added my tab control to a form, gave it a few tabpages and ran it. Sure enough, every value printed as I clicked and poked and prodded was a positive integer. I started programming with .NET, so I don't have any prior experience with windows messages and I'm obviously missing something, but I can't figure it out. I've been searching the documentation for the last hour trying to make sense of it, with no luck.
-
:confused:Forgive me, but I'm a little confused. Heath Stewart wrote: private const int TCN_SELCHANGING = -552; I'm assuming that's from CommCtrl.h :
#define TCN_FIRST (0U-550U) // tab control
...
#define TCN_SELCHANGING (TCN_FIRST - 2)But the MSDN library has the following definition for WindowProc:
LRESULT CALLBACK WindowProc( HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);Which leads me to believe that uMsg will never be -552. To test it, I derived a control from
TabControl
and overrodeWndProc
as such:protected override void WndProc(ref Message m) {
Console.WriteLine(m.Msg);
base.WndProc (ref m);
}I added my tab control to a form, gave it a few tabpages and ran it. Sure enough, every value printed as I clicked and poked and prodded was a positive integer. I started programming with .NET, so I don't have any prior experience with windows messages and I'm obviously missing something, but I can't figure it out. I've been searching the documentation for the last hour trying to make sense of it, with no luck.
The fact that the number looks negative to you doesn't mean it's negative to a CPU. The runtime doesn't really care, either, except when it comes time to compare or display the value. The Type of the value type will determine if it can be negative or not. A 32-bit signed integer such as -552 is the same as the 32-bit unsigned integer 0xfffffdd8. When taking into account unsigned equivalents, this is 4294966744. Unfortunately, this only would've applied if the const was defined as a
UInt32
(uint
keyword). Sorry about the confusion. Instead, define your constantint
as the hex value given above and it should work.Microsoft MVP, Visual C# My Articles
-
The fact that the number looks negative to you doesn't mean it's negative to a CPU. The runtime doesn't really care, either, except when it comes time to compare or display the value. The Type of the value type will determine if it can be negative or not. A 32-bit signed integer such as -552 is the same as the 32-bit unsigned integer 0xfffffdd8. When taking into account unsigned equivalents, this is 4294966744. Unfortunately, this only would've applied if the const was defined as a
UInt32
(uint
keyword). Sorry about the confusion. Instead, define your constantint
as the hex value given above and it should work.Microsoft MVP, Visual C# My Articles
Please forgive me if I'm being exceptionally dense, but it's still not clear to me. Heath Stewart wrote: A 32-bit signed integer such as -552 is the same as the 32-bit unsigned integer 0xfffffdd8. When taking into account unsigned equivalents, this is 4294966744. Thank you for explaining this. This part makes perfect sense to me, that's not the problem. The range for
TabControl
messages, according to CommCtrl.h is -550 to -580. None of the value I get from my tab control is even close to this range. Switching back and forth between pages, selecting controls on the pages, hiding and showing the form... all of them return message IDs of 0-675 Heath Stewart wrote: define your constant int as the hex value given above and it should work. I can't do this because 0xffffdd8 is beyond the valid range of values for anint
. Using along
orunit
would be pointless because theMsg
poperty ofMessage
is of typeint
.const int TCN_SELCHANGING = -552;
protected override void WndProc(ref Message m) {
Debug.Assert(m.Msg != TCN_SELCHANGING); //Never fires
base.WndProc (ref m);
} -
Please forgive me if I'm being exceptionally dense, but it's still not clear to me. Heath Stewart wrote: A 32-bit signed integer such as -552 is the same as the 32-bit unsigned integer 0xfffffdd8. When taking into account unsigned equivalents, this is 4294966744. Thank you for explaining this. This part makes perfect sense to me, that's not the problem. The range for
TabControl
messages, according to CommCtrl.h is -550 to -580. None of the value I get from my tab control is even close to this range. Switching back and forth between pages, selecting controls on the pages, hiding and showing the form... all of them return message IDs of 0-675 Heath Stewart wrote: define your constant int as the hex value given above and it should work. I can't do this because 0xffffdd8 is beyond the valid range of values for anint
. Using along
orunit
would be pointless because theMsg
poperty ofMessage
is of typeint
.const int TCN_SELCHANGING = -552;
protected override void WndProc(ref Message m) {
Debug.Assert(m.Msg != TCN_SELCHANGING); //Never fires
base.WndProc (ref m);
}It wouldn't be pointless to define a
uint
because you can always cast it to anint
upon comparing, but I wouldn't recommend it since this method should be fast (it has to process potentially a lot of messages, after all) and the extra instruction to cast (and possibly two more to store and retrieve depending on the code) is too expensive. Fortunately, I re-read the documentation forTCN_SELCHANGING
and noticed that this message is sent in the form of aWM_NOTIFY
message to the parent of theTabControl
. This little example works:using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class Test : Form
{
public static void Main()
{
Application.Run(new Test());
}
public Test()
{
TabControl tab = new TabControl();this.SuspendLayout(); this.Text = "Test"; this.Controls.Add(tab); tab.TabPages.Add(new TabPage("Page 1")); tab.TabPages.Add(new TabPage("Page 2")); tab.Dock = DockStyle.Fill; this.ResumeLayout();
}
private const int WM_NOTIFY = 0x004e;
private const int TCN_SELCHANGING = -552;
private struct NMHDR
{
public IntPtr hwndFrom;
public int idFrom;
public int code;
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NOTIFY)
{
NMHDR hdr = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
Console.WriteLine(hdr.code);
if (hdr.code == TCN_SELCHANGING)
m.Result = new IntPtr(1);
}
}
}Sorry for the mess. Most messages I deal with are sent directly and aren't
WM_NOTIFY
style messages. This gets a little quirky. You'll notice I marshal theLPARAM
parameter to anNMHDR
struct, which I then check the code. Getting to know the notification messages helps, I just feel dumb that I didn't fully read it before. Also note that I'm callingbase.WndProc
first. If you don't, Windows is likely to change theMessage.Result
field to 0 (or reset it to 0). You could also just not call it and return in this case if you get theWM_NOTIFY
message with theNMHDR.code
set toTCN_SELCHANGING
.Microsoft MVP, Visual C#[](</x-turndown)
-
It wouldn't be pointless to define a
uint
because you can always cast it to anint
upon comparing, but I wouldn't recommend it since this method should be fast (it has to process potentially a lot of messages, after all) and the extra instruction to cast (and possibly two more to store and retrieve depending on the code) is too expensive. Fortunately, I re-read the documentation forTCN_SELCHANGING
and noticed that this message is sent in the form of aWM_NOTIFY
message to the parent of theTabControl
. This little example works:using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class Test : Form
{
public static void Main()
{
Application.Run(new Test());
}
public Test()
{
TabControl tab = new TabControl();this.SuspendLayout(); this.Text = "Test"; this.Controls.Add(tab); tab.TabPages.Add(new TabPage("Page 1")); tab.TabPages.Add(new TabPage("Page 2")); tab.Dock = DockStyle.Fill; this.ResumeLayout();
}
private const int WM_NOTIFY = 0x004e;
private const int TCN_SELCHANGING = -552;
private struct NMHDR
{
public IntPtr hwndFrom;
public int idFrom;
public int code;
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NOTIFY)
{
NMHDR hdr = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
Console.WriteLine(hdr.code);
if (hdr.code == TCN_SELCHANGING)
m.Result = new IntPtr(1);
}
}
}Sorry for the mess. Most messages I deal with are sent directly and aren't
WM_NOTIFY
style messages. This gets a little quirky. You'll notice I marshal theLPARAM
parameter to anNMHDR
struct, which I then check the code. Getting to know the notification messages helps, I just feel dumb that I didn't fully read it before. Also note that I'm callingbase.WndProc
first. If you don't, Windows is likely to change theMessage.Result
field to 0 (or reset it to 0). You could also just not call it and return in this case if you get theWM_NOTIFY
message with theNMHDR.code
set toTCN_SELCHANGING
.Microsoft MVP, Visual C#[](</x-turndown)
That is a beautiful thing. I started programming with .NET and have always been somewhat uncomfortable when it comes to anything outside the framework, although I am reasonably adept at programming within it. Thank you, Heath, for your help. It's a small thing, I know, but it helped me tremendously.
-
It wouldn't be pointless to define a
uint
because you can always cast it to anint
upon comparing, but I wouldn't recommend it since this method should be fast (it has to process potentially a lot of messages, after all) and the extra instruction to cast (and possibly two more to store and retrieve depending on the code) is too expensive. Fortunately, I re-read the documentation forTCN_SELCHANGING
and noticed that this message is sent in the form of aWM_NOTIFY
message to the parent of theTabControl
. This little example works:using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class Test : Form
{
public static void Main()
{
Application.Run(new Test());
}
public Test()
{
TabControl tab = new TabControl();this.SuspendLayout(); this.Text = "Test"; this.Controls.Add(tab); tab.TabPages.Add(new TabPage("Page 1")); tab.TabPages.Add(new TabPage("Page 2")); tab.Dock = DockStyle.Fill; this.ResumeLayout();
}
private const int WM_NOTIFY = 0x004e;
private const int TCN_SELCHANGING = -552;
private struct NMHDR
{
public IntPtr hwndFrom;
public int idFrom;
public int code;
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NOTIFY)
{
NMHDR hdr = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
Console.WriteLine(hdr.code);
if (hdr.code == TCN_SELCHANGING)
m.Result = new IntPtr(1);
}
}
}Sorry for the mess. Most messages I deal with are sent directly and aren't
WM_NOTIFY
style messages. This gets a little quirky. You'll notice I marshal theLPARAM
parameter to anNMHDR
struct, which I then check the code. Getting to know the notification messages helps, I just feel dumb that I didn't fully read it before. Also note that I'm callingbase.WndProc
first. If you don't, Windows is likely to change theMessage.Result
field to 0 (or reset it to 0). You could also just not call it and return in this case if you get theWM_NOTIFY
message with theNMHDR.code
set toTCN_SELCHANGING
.Microsoft MVP, Visual C#[](</x-turndown)
Hi. I just tried this and it works just fine. Great :) However, it is still possible to change the tab by pressing the default shortcutkey (ctrl + tab). I want to be able to control this too. My guess is that all I have to do is add a check do compare to a different messagenumber. Is this correct? If so, Do you have a link to som docs where I can find this number? I would also like to know if it is possible to detect which tabpage he pressed. for example: If the user is on tabpage1, he can go to tabpage 2 but NOT to tabpage 3. To be able to do this, I need to know which tabpage he clicked Thanks so far.
-
Hi. I just tried this and it works just fine. Great :) However, it is still possible to change the tab by pressing the default shortcutkey (ctrl + tab). I want to be able to control this too. My guess is that all I have to do is add a check do compare to a different messagenumber. Is this correct? If so, Do you have a link to som docs where I can find this number? I would also like to know if it is possible to detect which tabpage he pressed. for example: If the user is on tabpage1, he can go to tabpage 2 but NOT to tabpage 3. To be able to do this, I need to know which tabpage he clicked Thanks so far.
As with all Windows Common Controls, you can find these in the Platform SDK on MSDN Library Online[^]. For the common controls, see http://msdn.microsoft.com/library/en-us/shellcc/platform/commctls/wincontrols.asp[^]. Under "Individual Controls" you'll find documentation on the control messages you want. You should take a look at the rest of the stuff, too. You can try overriding
OnKeyDown
, although IIRC Ctrl+Tab is handled a little differently. You can find more information about handling the notification messages in the application pump (using anIMessageFilter
perhaps) if this is the case in the .NET Framework SDK (find the interface in the help index I mentioned) and in the Platform SDK. In order to determine which tab he clicked on, you're going to have to do a lot of this yourself. You can override theOnMouseDown
event which gives you coordinates relative to the client (TabControl
). Then enumerate yourTabPage
s and callTabControl.GetTabRect
with each one (or rather, their index) and useRectangle.Contains
to see which they clicked on. Other ways most likely won't work because you stopped the tabs from changing, so the other messages that fire events in .NET won't be sent and received. In cases when you derive a control, you should override theOn_Event_
method, callingbase.On_Event_
before or after your code (depending on the circumstances). Handle these events from outside the class. This improves performance slightly and makes for a better polymorphic design.Microsoft MVP, Visual C# My Articles