CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 2 of 2 FirstFirst 12
Results 16 to 20 of 20
  1. #16
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Red face Re: Single instance app works - still feels a bit like a hack though...

    Late update...

    I just happened to notice that flashing the task bar button respectively window caption actually is the intended and documented behavior of Form::Activate() in case the calling application isn't the active one:

    Quote Originally Posted by MSDN
    Activating a form brings it to the front if this is the active application, or it flashes the window caption if this is not the active application. [...]


    So now the question is: Is there any (preferably "official") way for an application to activate itself? At least I didn't find one in either the System::Windows::Forms::Application or System::Environment (rather less likely) class.
    Last edited by Eri523; July 28th, 2011 at 05:49 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  2. #17
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Re: Single instance app works - still feels a bit like a hack though...

    I just revisited this topic because I had a new idea how to implement it: There definitely seems to be no way for an app to activate itself. Perhaps the Windows developers considered the possibility of self-activation a risk - at least an annoyance risk? However, of course, apps can be activated by other apps, like Process Explorer for instance.

    Based on this consideration, I implemented a new way of activation of the primary app instance when another one is started. The goal was to call BringWindowToTop() on the primary instance main window from the non-primary instance. The problem just was that the non-primary instance doesn't know its handle. So I introduced two new registered window messages, one with which the non-primary instance announces its own main window handle and another one that the primary instance uses to send back its own main window handle to the non-primary window handle it just got to know.

    Following is the implementation. (I also attached the new version of the demo project to the post.) I'm not posting the modified WndMessageHolder since the changes I applied to it are mostly natural and can be deduced from the code posted. Of course it is part of the attached project. Also, I have reverted the BringUpTest.cpp file which contains the main() function back to the state in which it was created by the IDE, since all the instance switching work now is done from the form class because it needs a running message loop in order to catch the response from the primary instance.

    Code:
    // Form1.cpp
    
    #include "stdafx.h"
    
    #include "Form1.h"
    #include "WndMessageHolder.h"
    
    using namespace BringUpTest;
    
    // Message handler for primary instance activation
    
    void Form1::WndProc(Message %msg)
    {
      WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
      if (msg.Msg == wmh->WM_BRINGUP) BringMeUp();
      else if (msg.Msg == wmh->WM_MESSAGEECHO_ADVERTISE) MessageEchoAdvertiseHandler(msg.WParam);
      else if (msg.Msg == wmh->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE) MessageEchoAdvertisementResponseHandler(msg.WParam);
      __super::WndProc(msg);
    }
    
    // Handles WM_BRINGUP message
    
    void Form1::BringMeUp()
    {
      switch (m_ism) {
        case InstanceSwitchMethod::Classic:
          // "Classic" version according to TheGreatCthulhu's suggestion - unfortunately doesn't
          // really work completely... (see CG forum thread)
    
          if (WindowState == FormWindowState::Minimized)
            WindowState = FormWindowState::Normal;
    
          Activate();
    
          break;
    
        case InstanceSwitchMethod::Alternative:
          // Alternative version - works but unfortunately looks hackish...
    
          if (WindowState != FormWindowState::Minimized)
            WindowState = FormWindowState::Minimized;
          WindowState = FormWindowState::Normal;
          // Activate();
          break;
    
        case InstanceSwitchMethod::MessageEcho:
          Trace::Fail("Message Echo method does not use the WM_BRINGUP message");
          break;
      }
    }
    
    // Handles WM_MESSAGEECHO_ADVERTISE message - gets passed the non-primary instance main window handle
    
    void Form1::MessageEchoAdvertiseHandler(IntPtr ipHwndNonPrimary)
    {
      bool bResult = WndMessageHolder::PostMessage_(ipHwndNonPrimary,
        WndMessageHolder::GetInstance()->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE, Handle, (IntPtr)0);
      Trace::Assert(bResult, String::Format("Posting WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message failed (code {0})",
        WndMessageHolder::GetLastError_()));
    }
    
    // Handles WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message - gets passed the primary instance main window handle
    
    void Form1::MessageEchoAdvertisementResponseHandler(IntPtr ipHwndPrimary)
    {
      bool bResult = WndMessageHolder::BringWindowToTop_(ipHwndPrimary);
      Trace::Assert(bResult, String::Format("Calling BringWindowToTop() on primary instance main window handle failed (code {0})",
        WndMessageHolder::GetLastError_()));
      tmrMessageEchoTimeout->Stop();
      Close();
    }
    
    // Event handler to switch between the two instance switching modes
    
    System::Void Form1::InstanceSwitchModeChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbMessageEcho->Checked) {
        if (!m_evtMessageEchoSignal) {
          m_evtMessageEchoSignal = gcnew EventWaitHandle(true, EventResetMode::ManualReset, m_strMessageEchoSignalEvent);
        } else m_evtMessageEchoSignal->Set();
        m_ism = InstanceSwitchMethod::MessageEcho;
      } else {
        if (m_evtMessageEchoSignal) m_evtMessageEchoSignal->Reset();
        if (rbClassic->Checked) m_ism = InstanceSwitchMethod::Classic;
        else if (rbAlt->Checked) m_ism = InstanceSwitchMethod::Alternative;
        else Trace::Fail("What the heck... Unexpected outcome of instance switching mode change");
      }
    }
    
    System::Void Form1::Form1_Load(System::Object^  sender, System::EventArgs^  e)
    {
      String ^strInstanceMutex = "{BE3A098C-D815-43e7-B8E2-5EC55CBC5306}";
    #ifdef _DEBUG
      strInstanceMutex += "_debug";
    #endif
    
      bool bIMTheOne;
      m_mtxInstance = gcnew Mutex(true, strInstanceMutex, bIMTheOne);
      if (!bIMTheOne) {
        WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
        m_evtMessageEchoSignal = gcnew EventWaitHandle(false, EventResetMode::ManualReset, m_strMessageEchoSignalEvent);
    
        if (m_evtMessageEchoSignal->WaitOne(0)) {
    
          // The event isn't actually part of the Message Echo mechanism. It's just used in this demo
          // to signal the non-primary instance to use the Message Echo method.
    
          // Advertise our own main window handle:
    
          Trace::WriteLine("BringUpTest already running, sending WM_MESSAGEECHO_ADVERTISE message...");
          bool bResult = WndMessageHolder::PostMessage_((IntPtr)HWND_BROADCAST, wmh->WM_MESSAGEECHO_ADVERTISE, Handle, (IntPtr)0);
          Trace::Assert(bResult, String::Format("Broadcasting WM_MESSAGEECHO_ADVERTISE message failed (code {0})",
            WndMessageHolder::GetLastError_()));
    
          // The primary instance is now supposed to respond with a WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message
          // carrying its own main window handle
    
          // Make my own main window invisible and start timeout:
    
          Visible = false;
          tmrMessageEchoTimeout->Start();
    
        } else {
    
          Trace::WriteLine("BringUpTest already running, sending WM_BRINGUP message...");
          bool bResult = WndMessageHolder::PostMessage_((IntPtr)HWND_BROADCAST, wmh->WM_BRINGUP, (IntPtr)0, (IntPtr)0);
          Trace::Assert(bResult, String::Format("Broadcasting WM_BRINGUP message failed (code {0})",
            WndMessageHolder::GetLastError_()));
          Close();
    
        }
      }
    }
    
    System::Void Form1::tmrMessageEchoTimeout_Tick(System::Object^  sender, System::EventArgs^  e)
    {
      // This handler actually should never get called
    
      Trace::Fail("BringUpTest: Message Echo mechanism timed out");
    }
    It's plain to see that this is considerably more complicated than the Alternative approach used in the earlier posts. I probably won't implement it that way in the real-life app at all, not only because of the increased complexity, but also because the older, somewhat hackish approach leads to a considerably smoother repaint of the primary instance that just has been brought to the top. (At least that's the case on my own system. Anyone who wants to test that on something else than an XP SP3 is welcome.) At least it's cleaner style, IMO...

    Comments welcome!
    Last edited by Eri523; December 17th, 2011 at 04:47 PM. Reason: Removed attachment - has been superseded by the one attached to the next post
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  3. #18
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Re: Single instance app works - still feels a bit like a hack though...

    Oops! There has been a serious bug in the demo project attached to the last post: Due to failure to properly adjust a copy-pasted piece of code, the program effectively used a WM_NULL for the advertisement response used in the Message Echo method instead of the registered message. This seemingly worked until some other program attempted to check the responsiveness of the test app, which is usually done by sending a WM_NULL that is supposed to be ignored by the app that receives it. Due to the bug, the responsiveness check resulted in an assertion failure since, of course, the message sent in this process doesn't carry a valid window handle in its wParam.

    To fix the bug, those who don't want to download the new version of the demo project just need to change line 31 of WndMessageHolder.cpp to this:

    Code:
      WM_MESSAGEECHO_ADVERTISEMENTRESPONSE = RegisterWindowMessage(tstrMessageEchoAdvertisementResponse);
    However, the new version of the demo project attached to this post also implements yet another instance switching method called Deposited Handle that I created because I thought maybe the message traffic between the two instances was responsible for the awkward repainting occuring when using the Message Echo method. The Deposited Handle method works by the primary instance placing its own window handle in a registry entry to be retrieved by the secondary instance.

    Simply calling BringWindowToTop() on the primary instance window had no observable effect, though. So I resorted to having the non-primary instance post a WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message to itself as a workaround which basically works, but leads to the same repainting behavior as the Message Echo method.

    At any rate, following is the current version of the demo project's Form1.cpp. In addition to the changes already mentioned, I mainly cleaned up the handling of radio button state changes. I hope the comments I placed in the code answer at least most of the questions remaining after the explanations above.

    Code:
    // Form1.cpp
    
    #include "stdafx.h"
    
    #include "Form1.h"
    #include "WndMessageHolder.h"
    
    using namespace BringUpTest;
    
    // Message handler for primary instance activation
    
    void Form1::WndProc(Message %msg)
    {
      WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
      if (msg.Msg == wmh->WM_BRINGUP) BringMeUp();
      else if (msg.Msg == wmh->WM_MESSAGEECHO_ADVERTISE) MessageEchoAdvertiseHandler(msg.WParam);
      else if (msg.Msg == wmh->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE) MessageEchoAdvertisementResponseHandler(msg.WParam);
      __super::WndProc(msg);
    }
    
    // Handles WM_BRINGUP message
    
    void Form1::BringMeUp()
    {
      switch (m_ism) {
        case InstanceSwitchMethod::Classic:
          // "Classic" version according to TheGreatCthulhu's suggestion - unfortunately doesn't
          // really work completely... (see CG forum thread)
    
          if (WindowState == FormWindowState::Minimized)
            WindowState = FormWindowState::Normal;
    
          Activate();
    
          break;
    
        case InstanceSwitchMethod::Alternative:
          // Alternative version - works but unfortunately looks hackish...
    
          if (WindowState != FormWindowState::Minimized)
            WindowState = FormWindowState::Minimized;
          WindowState = FormWindowState::Normal;
          // Activate();
          break;
    
        default:
          Trace::Fail("Message Echo method and Deposited Handle method don't use the WM_BRINGUP message");
          break;
      }
    }
    
    // Handles WM_MESSAGEECHO_ADVERTISE message - gets passed the non-primary instance main window handle
    
    void Form1::MessageEchoAdvertiseHandler(IntPtr ipHwndNonPrimary)
    {
      bool bResult = WndMessageHolder::PostMessage_(ipHwndNonPrimary,
        WndMessageHolder::GetInstance()->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE, Handle, (IntPtr)0);
      Trace::Assert(bResult, String::Format("Posting WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message failed (code {0})",
        WndMessageHolder::GetLastError_()));
    }
    
    // Handles WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message - gets passed the primary instance main window handle
    
    void Form1::MessageEchoAdvertisementResponseHandler(IntPtr ipHwndPrimary)
    {
      bool bResult = WndMessageHolder::BringWindowToTop_(ipHwndPrimary);
      Trace::Assert(bResult, String::Format("Calling BringWindowToTop() on primary instance main window handle failed (code {0})",
        WndMessageHolder::GetLastError_()));
      tmrMessageEchoTimeout->Stop();
      Close();
    }
    
    // Event handlers to switch between the two instance switching modes
    
    System::Void Form1::rbClassic_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbClassic->Checked) m_ism = InstanceSwitchMethod::Classic;
    }
    
    System::Void Form1::rbAlt_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbAlt->Checked) m_ism = InstanceSwitchMethod::Alternative;
    }
    
    System::Void Form1::rbMessageEcho_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbMessageEcho->Checked) {
        if (!m_evtMessageEchoSignal) {
          m_evtMessageEchoSignal = gcnew EventWaitHandle(true, EventResetMode::ManualReset, m_strMessageEchoSignalEvent);
        } else m_evtMessageEchoSignal->Set();
        m_ism = InstanceSwitchMethod::MessageEcho;
      } else m_evtMessageEchoSignal->Reset();
    }
    
    System::Void Form1::rbDepositedHandle_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (rbDepositedHandle->Checked) {
        Application::UserAppDataRegistry->SetValue("PrimaryWindowHandle", int(Handle));
        m_bRegistryEntryCreated = true;
        m_ism = InstanceSwitchMethod::DepositedHandle;
      } else RemoveRegistryEntry();
    }
    
    // Tear down our entire registry branch in order to leave zero registry footprint
    
    void Form1::RemoveRegistryEntry()
    {
      if (m_bRegistryEntryCreated) {
        RegistryKey ^rkCompany = Registry::CurrentUser->OpenSubKey("Software\\" + Application::CompanyName, true);
        rkCompany->DeleteSubKeyTree(Application::ProductName);
        if (rkCompany->SubKeyCount + rkCompany->ValueCount == 0) {  // Delete company key if it's empty now
          rkCompany->Close();
          Registry::CurrentUser->OpenSubKey("Software", true)->DeleteSubKey(Application::CompanyName);
        }
        m_bRegistryEntryCreated = false;
      }
    }
    
    System::Void Form1::Form1_Load(System::Object^  sender, System::EventArgs^  e)
    {
      String ^strInstanceMutex = "{BE3A098C-D815-43e7-B8E2-5EC55CBC5306}";
    #ifdef _DEBUG
      strInstanceMutex += "_debug";
    #endif
    
      bool bIMTheOne;
      m_mtxInstance = gcnew Mutex(true, strInstanceMutex, bIMTheOne);
      if (!bIMTheOne) {
        WndMessageHolder ^wmh = WndMessageHolder::GetInstance();
    
        // Don't use Application::UserAppDataRegistry here to avoid creating the key if it doesn't exist already:
    
        Object ^objRegValue = Registry::GetValue(String::Format("HKEY_CURRENT_USER\\Software\\{0}\\{1}\\{2}",
          Application::CompanyName, Application::ProductName, Application::ProductVersion), "PrimaryWindowHandle", nullptr);
        if (objRegValue) {
    
          // The registry value exists, so use the Deposited Handle method. Calling BringWindowToTop()
          // on the stored window handle from here doesn't report failure but has no effect either -
          // for unknown reasons. Posting a message to ourselves works but has no visible advantage
          // over the Message Echo method.
    
          bool bResult = WndMessageHolder::PostMessage_(this, wmh->WM_MESSAGEECHO_ADVERTISEMENTRESPONSE,
            IntPtr(safe_cast<int>(objRegValue)), (IntPtr)0);
          Trace::Assert(bResult, String::Format("Posting WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message to self failed (code {0})",
            WndMessageHolder::GetLastError_()));
    
        } else {
          m_evtMessageEchoSignal = gcnew EventWaitHandle(false, EventResetMode::ManualReset, m_strMessageEchoSignalEvent);
    
          if (m_evtMessageEchoSignal->WaitOne(0)) {
    
            // The event isn't actually part of the Message Echo mechanism. It's just used in this demo
            // to signal the non-primary instance to use the Message Echo method.
    
            // Advertise our own main window handle:
    
            Trace::WriteLine("BringUpTest already running, sending WM_MESSAGEECHO_ADVERTISE message...");
            bool bResult = WndMessageHolder::PostMessage_((IntPtr)HWND_BROADCAST, wmh->WM_MESSAGEECHO_ADVERTISE, Handle, (IntPtr)0);
            Trace::Assert(bResult, String::Format("Broadcasting WM_MESSAGEECHO_ADVERTISE message failed (code {0})",
              WndMessageHolder::GetLastError_()));
    
            // The primary instance is now supposed to respond with a WM_MESSAGEECHO_ADVERTISEMENTRESPONSE message
            // carrying its own main window handle
    
            // Make my own main window invisible and start timeout:
    
            Visible = false;
            tmrMessageEchoTimeout->Start();
    
          } else {
    
            Trace::WriteLine("BringUpTest already running, sending WM_BRINGUP message...");
            bool bResult = WndMessageHolder::PostMessage_((IntPtr)HWND_BROADCAST, wmh->WM_BRINGUP, (IntPtr)0, (IntPtr)0);
            Trace::Assert(bResult, String::Format("Broadcasting WM_BRINGUP message failed (code {0})",
              WndMessageHolder::GetLastError_()));
            Close();
    
          }
        }
      }
    }
    
    System::Void Form1::tmrMessageEchoTimeout_Tick(System::Object^  sender, System::EventArgs^  e)
    {
      // This handler actually should never get called
    
      Trace::Fail("BringUpTest: Message Echo mechanism timed out");
    }
    Attached Files Attached Files
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  4. #19
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Re: Single instance app works - still feels a bit like a hack though...

    [... A LOT of time passes - again... ]

    A thread I read along in the VC++ section the other day made me click around a bit on MSDN, like so many times, but this time I came across something that really arrested my attention, regarding the topic discussed in this thread here.

    From the documentation on the WM_SYSCOMMAND message:

    Quote Originally Posted by MSDN
    Parameters

    wParam
    The type of system command requested. This parameter can be one of the following values.

    [...]

    SC_HOTKEY
    0xF150

    Activates the window associated with the application-specified hot key. The lParam parameter identifies the window to activate.

    [...]

    Remarks

    [...]

    An application can carry out any system command at any time by passing a WM_SYSCOMMAND message to DefWindowProc. [...]

    [...]
    That looked quite promising for implementing a fifth instance switching method I called Fake Hotkey, which, like the Classic and Alternative methods, makes use of the registered WM_BRINGUP message:

    Code:
    // Handles WM_BRINGUP message
    
    void Form1::BringMeUp()
    {
      switch (m_ism) {
    
        // ...
    
        case InstanceSwitchMethod::FakeHotkey:
          // Actually the simplest of them all:
          // Just pass a WM_SYSCOMMAND with SC_HOTKEY to myself by calling the WndProc()
    
          WndProc(Message::Create(Handle, WM_SYSCOMMAND, (IntPtr)SC_HOTKEY, Handle));
          break;
    
        // ...
    
      }
    }
    
    // ...
    
    System::Void Form1::Form1_Load(System::Object^  sender, System::EventArgs^  e)
    {
    
      // ...
    
      bool bIMTheOne;
      m_mtxInstance = gcnew Mutex(true, strInstanceMutex, bIMTheOne);
      if (!bIMTheOne) {
    
        // ...
    
      } else {
        // Get myself a hotkey
    
        IntPtr lres =
          WndMessageHolder::SendMessage_(this, WM_SETHOTKEY, (IntPtr)((HOTKEYF_ALT | HOTKEYF_CONTROL) << 16 | 'B'), (IntPtr)0);
        Trace::WriteLine(String::Format("Sending WM_SETHOTKEY to self returned {0}", lres));
      }
    }
    Allocating such a hotkey for an app that just has a single top-level window may look pointless on first sight, but my tests showed that, apparently, the variety of the WM_SYSCOMMAND message I use otherwise has no effect.

    Now this looks like the clean and simple solution I have been looking for for about one year. As usual, any comments and critics are welcome, and I have attached the latest version of the demo project to this post.

    The next thing I think I'll do is implement that in the real-life app for field testing. However, I'll make the instance swiching mode switchable using undocumented app.config settings and key combinations, like I already had it before, just in case it turns out to cause trouble out there.

    BTW, while I tried to intentionally produce a negative during my recent tests, it turned out that the Classic method seemingly sometimes does work. Well, I think we already had something like that sometime around post #12...
    Attached Files Attached Files
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  5. #20
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Re: Single instance app works - still feels a bit like a hack though...

    I cheered too quickly again: Fake Hotkey was another one that may or may not work. However, I found out a bit more what "may or may not" means in concrete: It works when BringUpTest is started from the VC++ IDE and does not work otherwise. The same seems to apply to the Classic method. There also seem to be nuances regarding whether just one or both instances involved in a test have been started from the IDE and/or inside the debugger, but I didn't reseach that in more detail: It would be quite ridiculous anyway to make an installed VC++ a requirement for the real-life app...

    Using Process Explorer I found out that one difference between BringUpTest being started inside the IDE or outside is that when stated inside the IDE, it has the SeDebugPrivilege enabled (even when not actually started in the debugger), otherwise not. So I tested whether acquiring the privilege in BringUpTest itself would help. (It took me an hour or two browsing MSDN to find out how to do that, and I found the answer, yet again, in the Process class. One of these "why didn't I look there first?" moments... Still not a complete waste of time, though, since I found the answers to some questions of the type "how can I do that, if at all?" I've been asking myself for some time, specifically in the System::Diagnostics::Debugger class.)

    Acquirig the privilege from within BringUpTest turned out not to help, so I thought it might help if the privilege gets inherited (like from the IDE or debugger). I wrote a small starter program for that purpose and could verify that the privilege in fact got inherited, yet it still didn't help. There must be something more to it...

    But even if enabling SeDebugPrivilege would have helped, I wouldn't have been really happy with it: At least the AV I use kindly asks whether to allow a program to acquire that privilege unless the program isn't already (explicitly or implicitly) classified as trusted. And I think just being happy about neater looking code doesn't quite justify potentially scaring users with not actually really needed AV warnings. (I've frequently asked myself anyway why so many programs that apparently don't have anything to do with debugging or other low-level activities attempt to acquire the privilege. And that's by far not just programs that are suspicious in some way.)

    So, as we all know since the very start of that thread, the Alternative method does work. I think after almost one year of fiddling around, it's finally time to give up and be satisfied with what I have.

    This thread now may deserve something like a [RESOLVED :sigh:] or a [ResolvedAttribute(Success=false)]. I know something like that currently isn't available, but perhaps we'll get it with the upcoming forum software update...

    At least this thread leaves five variants of instance switching for others to play with, and perhaps someone may find out something interesting about it sometime...
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

Page 2 of 2 FirstFirst 12

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured