Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C#
  4. catch tabpage change

catch tabpage change

Scheduled Pinned Locked Moved C#
question
9 Posts 3 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    troels_sorensen
    wrote on last edited by
    #1

    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...

    H 1 Reply Last reply
    0
    • T troels_sorensen

      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...

      H Offline
      H Offline
      Heath Stewart
      wrote on last edited by
      #2

      Yes, of course it's called after. It is SelectedIndexChang**ed** afterall. In order to prevent this, you're going to have to derive from TabControl and override WndProc and handle the TCN_SELCHANGING (-552). Return true 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

      A 1 Reply Last reply
      0
      • H Heath Stewart

        Yes, of course it's called after. It is SelectedIndexChang**ed** afterall. In order to prevent this, you're going to have to derive from TabControl and override WndProc and handle the TCN_SELCHANGING (-552). Return true 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

        A Offline
        A Offline
        Anonymous
        wrote on last edited by
        #3

        :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 overrode WndProc 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.

        H 1 Reply Last reply
        0
        • A Anonymous

          :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 overrode WndProc 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.

          H Offline
          H Offline
          Heath Stewart
          wrote on last edited by
          #4

          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 constant int as the hex value given above and it should work.

          Microsoft MVP, Visual C# My Articles

          A 1 Reply Last reply
          0
          • H Heath Stewart

            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 constant int as the hex value given above and it should work.

            Microsoft MVP, Visual C# My Articles

            A Offline
            A Offline
            Anonymous
            wrote on last edited by
            #5

            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 an int. Using a long or unit would be pointless because the Msg poperty of Message is of type int.

            const int TCN_SELCHANGING = -552;
            protected override void WndProc(ref Message m) {
            Debug.Assert(m.Msg != TCN_SELCHANGING); //Never fires
            base.WndProc (ref m);
            }

            H 1 Reply Last reply
            0
            • A Anonymous

              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 an int. Using a long or unit would be pointless because the Msg poperty of Message is of type int.

              const int TCN_SELCHANGING = -552;
              protected override void WndProc(ref Message m) {
              Debug.Assert(m.Msg != TCN_SELCHANGING); //Never fires
              base.WndProc (ref m);
              }

              H Offline
              H Offline
              Heath Stewart
              wrote on last edited by
              #6

              It wouldn't be pointless to define a uint because you can always cast it to an int 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 for TCN_SELCHANGING and noticed that this message is sent in the form of a WM_NOTIFY message to the parent of the TabControl. 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 the LPARAM parameter to an NMHDR 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 calling base.WndProc first. If you don't, Windows is likely to change the Message.Result field to 0 (or reset it to 0). You could also just not call it and return in this case if you get the WM_NOTIFY message with the NMHDR.code set to TCN_SELCHANGING.

              Microsoft MVP, Visual C#[](</x-turndown)

              A T 2 Replies Last reply
              0
              • H Heath Stewart

                It wouldn't be pointless to define a uint because you can always cast it to an int 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 for TCN_SELCHANGING and noticed that this message is sent in the form of a WM_NOTIFY message to the parent of the TabControl. 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 the LPARAM parameter to an NMHDR 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 calling base.WndProc first. If you don't, Windows is likely to change the Message.Result field to 0 (or reset it to 0). You could also just not call it and return in this case if you get the WM_NOTIFY message with the NMHDR.code set to TCN_SELCHANGING.

                Microsoft MVP, Visual C#[](</x-turndown)

                A Offline
                A Offline
                Anonymous
                wrote on last edited by
                #7

                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.

                1 Reply Last reply
                0
                • H Heath Stewart

                  It wouldn't be pointless to define a uint because you can always cast it to an int 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 for TCN_SELCHANGING and noticed that this message is sent in the form of a WM_NOTIFY message to the parent of the TabControl. 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 the LPARAM parameter to an NMHDR 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 calling base.WndProc first. If you don't, Windows is likely to change the Message.Result field to 0 (or reset it to 0). You could also just not call it and return in this case if you get the WM_NOTIFY message with the NMHDR.code set to TCN_SELCHANGING.

                  Microsoft MVP, Visual C#[](</x-turndown)

                  T Offline
                  T Offline
                  troels_sorensen
                  wrote on last edited by
                  #8

                  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.

                  H 1 Reply Last reply
                  0
                  • T troels_sorensen

                    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.

                    H Offline
                    H Offline
                    Heath Stewart
                    wrote on last edited by
                    #9

                    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 an IMessageFilter 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 the OnMouseDown event which gives you coordinates relative to the client (TabControl). Then enumerate your TabPages and call TabControl.GetTabRect with each one (or rather, their index) and use Rectangle.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 the On_Event_ method, calling base.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

                    1 Reply Last reply
                    0
                    Reply
                    • Reply as topic
                    Log in to reply
                    • Oldest to Newest
                    • Newest to Oldest
                    • Most Votes


                    • Login

                    • Don't have an account? Register

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • World
                    • Users
                    • Groups