WPF Sample Series – WPF MVC TabControl MDI and CommandBindings

February 24, 2008

This is the next sample in the WPF Sample Applications Series. The purpose of the Sample Series is to provide concise code solutions for specific programming tasks. This sample provides a brief description of the problem, the solution and full source code.

This Sample assumes that you understand the basics of WPF RoutedCommands.  If not read up on them in the Visual Studio help files, Code Project articles, etc. and let’s get into this!

WPF Rock Star Josh Smith and I have been having deep discussions on WPF business applications, designer & developer roles, MDI, MVC and RoutedCommands.  Regardless of which specific topic we were exploring the discussion always seems to come back to decoupling. 

  • Decoupling of designers and developers
  • Decoupling of UI and business logic
  • Decoupling of UI and RoutedCommands
  • Decoupling of MDI business forms from the host window
  • Decoupling of … from …

Whenever I try new techniques or architectures I really want to be sure what is going on.  This helps me take advantage of the benefits and stay away from any bumps in the carpet.

I’m looking at using RoutedCommands for the ToolBar controls in my WPF application.  In fact, Josh has recently posted, Zen and the Art of WPF that covers using RoutedCommands.  I highly recommand reading this post. 

My application uses the WPF TabControl for MDI management.  When the user selects a form from the menu, a new TabItem is created, that form is added to the TabItem.Content and the TabItem is then added to the TabControl.Items collection.  Pretty simple actually. 

When I use the word, “form” what I’m really talking about is a WPF UserControl that contains the controls that make up the UI of the form.  Working with UserControls in Expression Blend is super simple which is one reason I use them to build my application.  In a “real world” application the form UserControls are loaded from different assemblies, which make for easy large application partitioning.  With the forms as UserControls with almost no UI code this simplifies the designer and developer roles and provides the ability to have clean separation if desired.

So I was thinking, if each form has 5 – 10 possible RoutedCommands and the user opened 10 forms (TabItems), then I could possibly have 100 RoutedCommand bindings active in my application.  My thoughts starting drifting off to how often does the CanExecute method get called on a RoutedCommand?  Will the CanExecute method get called for every form (TabItem) even though that form is not visible?  Do you know the answers to these questions?

Oh, one last thing before we get into the meat.  This is my first attempt at official MVC.  While all my previous ASP.NET and WPF business applications had business logic execution code where it belongs, this is the first time I’m actually trying to do MVC and call it MVC.  This application lacks the MVC Model piece.  We are only implementing the Controller and the View.

Context Of This Article

If your application is small with a few forms then some of what I present may be overkill.  These techniques to manage the number of active RoutedCommands bindings are targeted at business applications with the potential of many forms being open at once.  Please keep this in mind when reading the article as the information presented is specifically targeted to these types of business applications where the developer feels it is desirable to manage the number of these bindings.

This Sample Covers

  • Decoupling RoutedCommand execution from the UI
  • MDI RoutedCommand Management
  • Brief Look at WPF MDI using the TabControl

Decoupling RoutedCommand execution from the UI

This application has two UserControls that are used as forms, BetterWay and NotSoGoodWay.  The only difference between the two is how the RoutedCommand bindings are managed.  We will get to RoutedCommand binding manangement in a bit.

Below is the XAML and code for the BetterWay UserControl

<local:UserControlBase x:Class="BetterWay"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:TestCommandBindings">
    
    <StackPanel>
        <StackPanel x:Name="layoutRoot">
        </StackPanel>
        <TextBox Margin="10" Text="Hi Better Way" />
    </StackPanel>   
    
</local:UserControlBase>

The buttons will be added at run-time to the StackPanel.  The TextBox gives us a control to look at.

Public Class BetterWay
  Inherits UserControlBase

  'this is my business layer controller
  Private _objController As New Controller

  Private Sub BetterWay_Initialized(ByVal sender As Object, _
                                    ByVal e As System.EventArgs) Handles Me.Initialized

    ButtonFactory(ApplicationCommands.CancelPrint)
    ButtonFactory(ApplicationCommands.Copy)
    ButtonFactory(ApplicationCommands.Cut)
    ButtonFactory(ApplicationCommands.Find)
    ButtonFactory(ApplicationCommands.Help)
    ButtonFactory(ApplicationCommands.[New])
    ButtonFactory(ApplicationCommands.Paste)
    ButtonFactory(ApplicationCommands.Print)
    ButtonFactory(ApplicationCommands.PrintPreview)
    ButtonFactory(ApplicationCommands.Redo)
    ButtonFactory(ApplicationCommands.Save)
    ButtonFactory(Commands.CloseTabItem)

  End Sub

  Private Sub BetterWay_Loaded(ByVal sender As Object, _
                               ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

    'This get called twice when initially loaded into the tab 
    If Me.CommandBindings.Count = 0 Then

      Dim objBinding As CommandBinding

      For Each btn As Button In Me.layoutRoot.Children

        If btn.Command Is Commands.CloseTabItem Then
          'this command is handled in the base class
          objBinding = New CommandBinding(btn.Command, New Input.ExecutedRoutedEventHandler( _
              AddressOf CloseTabItemExecute), New Input.CanExecuteRoutedEventHandler(AddressOf _
              MyBase.CloseTabItemCanExecute))

        Else
          'these commands are handled in the business layer
          objBinding = New CommandBinding(btn.Command, New Input.ExecutedRoutedEventHandler( _
              AddressOf _objController.CmdExecute), New Input.CanExecuteRoutedEventHandler( _
              AddressOf _objController.CmdCanExecute))
        End If

        Me.CommandBindings.Add(objBinding)
      Next

    End If

  End Sub

  Private Sub BetterWay_Unloaded(ByVal sender As Object, _
                                 ByVal e As System.Windows.RoutedEventArgs) Handles Me.Unloaded
    Me.CommandBindings.Clear()

  End Sub

  Private Sub ButtonFactory(ByVal cmd As RoutedUICommand)

    Dim btn As New Button
    btn.Command = cmd
    btn.CommandTarget = Me
    btn.Content = cmd.Text

    If cmd Is Commands.CloseTabItem Then
      btn.Foreground = Brushes.Red
    End If

    Me.layoutRoot.Children.Add(btn)

  End Sub

  ''' <summary>
  ''' In a real application, if the form is dirty, give the user the
  ''' option to cancel the command, perform a save and then close or
  ''' just close and loose changes.
  ''' </summary>
  Protected Overrides Sub CloseTabItemExecute(ByVal sender As Object, _
                                              ByVal e As System.Windows.RoutedEventArgs)

    If Not _objController.IsDirty Then
      MyBase.OnCloseTabItem()
    End If

  End Sub

End Class

Let’s start with the topic decoupling RoutedCommand execution from the UI.  Look at the BetterWay_Loaded event handler.  Since the TabItem loaded event gets called multiple times, we only want to add the CommandBindings if there are none so we test for this and only add them if they are not present. 

When adding the CommandBindings, notice that the command execution for the custom command Commands.CloseTabItem takes place in the UI code and the other commands are handled in the MVC Controller or business layer.

I didn’t want my UI code cluttered with RoutedCommand event handlers if the only thing the event handler was going to do was pass it off to the Controller or business layer anyway.  So I pointed the RoutedCommand directly to the Controller.

For the purpose of this Sample, I’m using the same Controller for all forms and the Controller only has one RoutedCommand event handler.  I did this to make this demonstration simple.  In a “real” application, you would define event handlers for each of your UI commands in the Controller.  This is an example of decoupling the UI from the RoutedCommands and the execution of the RoutedCommand.

Some RoutedCommands should be handled by the UI code.  You’re not breaking any rules by doing this.  For example I’m handling the Close Tab button command in the UI code.  This is here so that the command handler code can make a decision and then raise a custom RoutedEvent to tell the UI to close the TabItem

You can’t raise RoutedEvents from the business layer and normally we don’t want the business layer talking directly to the UI.  Instead the UI asks the Controller or business layer if business object IsDirty before raising the RoutedEvent to close the TabItem

Josh has pointed out to me that many uses of MVC have the Controller talk to the View, but only via an interface.  The View would implement some interface and the Controller talks to that.

MDI RoutedCommand Management

This topic was the root source of the Sample and why I wrote this demo applcation and then this article.  I really wanted to find out how 50 or 100 CommandBindings from different TabItems would work together.

This is what I learned from this application.  If you have multiple TabItems that contain a form with active CommandBindings, those RoutedCommand.CanExecute methods will get called often.  This even applies to forms (TabItems) that are not visible (not the TabControl.SelectedItem.)

So I wanted to come up with a way to unhook the CommandBindings for forms that were not active.  I spoke with Josh several times about this and we came up with the following solution.

Give the BetterWay_Unloaded event handler a look.  This code clears the UserControls CommandBindings collection when it’s unloaded.  The UserControl.Unloaded event fires when the TabControl.SelectedItem is changed by the user clicking on another TabItem.Header.  For this business application I only want the CommandBindings loaded for the active TabItem.  So by removing them when the TabItem is not the TabControl.SelectedItem we accomplish this. 

In the NotSoGoodWay UserControl, I load the CommandBindings when the Button is created and do not clear the CommandBindings when the form is not active.  When the NotSoGoodWay UserControl is loaded into the TabControl, its RoutedCommand.CanExecute methods get called even when the form is not active (its TabItem is not visible).

Note on CanExecute

You can see that WPF calls these methods often which is a good feature.  When writing your CanExecute methods you should ensure that these methods run real fast.  These methods are not really the place to be hitting the database for an answer.  Simple and fast code is the way to go here.

To see this in action:

I created the below video and then changed some of the code after making the video and didn’t want to redo the video.  However the video covers the important concept of RoutedCommand Binding Management.

Silverlight Icon

The video links require Microsoft Silverlight 1.0.  If you do not have it, you will be prompted to install it when you click one of the links.  After the short installation is completed, close the browser window that you did the install in and re-click the video you want to watch.   You can also download it here. Windows XP or Vista required.

RoutedCommand.CanExecute Video

Testing The Application

  • Run the demo application using the Visual Studio Start Debugging command.
  • Size and position the Visual Studio Output window so that you can see the output.
  • From the menu select Customers.
  • You will see the CanExecute output messages.
  • Click on some commands and watch the output messages.
  • Click on the Close Tab button.  The TabItem will close.
  • From the menu select Customers.
  • From the menu select the Use Better Way menu item.  This will toggle the check mark.
  • From the menu select Sales.
    • When Sales is loaded it will load the NoSoGoodWay UserControl.
  • Click on some commands and watch the output messages. On the Sales messages will appear.
  • Click on the Customers TabItem.
  • Click on some commands and watch the output messages.
    • You will see Customers and Sales CanExecute messages in the output window.

Controller – Business Layer

Public Class Controller

  Public ReadOnly Property IsDirty() As Boolean
    Get
      'a real application will have logic for determining the state
      'of the business object.
      Return False
    End Get
  End Property

  Sub CmdCanExecute(ByVal sender As Object, _
                    ByVal e As CanExecuteRoutedEventArgs)

    e.CanExecute = True

    Debug.WriteLine(DirectCast(sender, UserControl).Name & " can execute " & _
        DirectCast(DirectCast(e.Command, System.Windows.Input.ICommand),  _
        System.Windows.Input.RoutedUICommand).Text)

  End Sub

  Public Sub CmdExecute(ByVal sender As System.Object, _
                        ByVal e As System.Windows.RoutedEventArgs)

    e.Handled = True

    Debug.WriteLine("---------------------------------------")

    Debug.WriteLine(DirectCast(sender, UserControl).Name & " executed " & _
        DirectCast(DirectCast(DirectCast(e,  _
        System.Windows.Input.ExecutedRoutedEventArgs).Command,  _
        System.Windows.Input.ICommand),  _
        System.Windows.Input.RoutedUICommand).Text)

    Debug.WriteLine("---------------------------------------")

  End Sub

End Class

This code is very straight forward.  CmdCanExecute method returns True and displays a message in the debugger Output Window.  The CmdExecute method handles the event and displays and message in the debugger Output Window.

In a “real” application each of these event handlers will actually call another method on the Controller to execute the code.  The reason for this is that you want your Controller or business layer to be able to support Unit Testing.  Josh has a fantasic Code Project article MVC to Unit Test in WPF that covers this topic.

I need to discuss a dependency that has been introduced into this code.  The event handler is dependent on WPF.  There are no RoutedEventArgs in ASP.NET, console applications or WinForms so these applications can’t use these handlers.

I’m spit balling here so my solution may change based on feedback.  You have several options.  You can place all the RoutedCommand event handlers in your UI code and then call methods on the Controller.  A lot of MVC solutions do this.  Or you can add an additional handler that WinForms, ASP.NET and console applications can all use.  That handler will call the same method that each of the RoutedEvent handlers will call. 

Why are you writing your code like this Karl?  Because my application is a WPF application.  I will have part of the application exposed on the web but only a very small percentage.  So for the sake of having cleaner WPF UI code, I’m writing my UI and Controller code like this.  You need to evaluate your application needs to determine what is the best course of action for you.

Brief Look at WPF MDI using the TabControl

This application gives you a glimpse of where I’m going with respect to my WPF Business Application series and the handling of MDI.  You can access the series from my Table of Contents page.

When I first started working on MDI in WPF I came to the conclusion that the TabControl would provide a simple and user friendly way to manage multiple documents or forms.  Nothing prevents the application from opening a modal or non-modal window on top of the application, but for the most part the application forms will be contained in the TabControl.

One issue that you have to deal with is how does the TabItem close itself?  We don’t want the TabItem content travelling up the Visual Tree, finding a TabControl and removing its parent TabItem.  For me the easiest method was for the TabItem content to raise a RoutedEvent that the TabControl would handle and  close the TabItem by removing it from its Items collection.

Since each form (UserControl) would need the ability to close itself I put most of the code in the UserControlBase class and derive all form UserControls from this class.

Public Class UserControlBase
  Inherits System.Windows.Controls.UserControl

  Public Shared ReadOnly CloseTabItemRoutedEvent As RoutedEvent = _
      EventManager.RegisterRoutedEvent("CloseTabItem", RoutingStrategy.Bubble, _
      GetType(RoutedEventHandler), GetType(UserControlBase))

  Public Custom Event CloseTabItem As RoutedEventHandler

    AddHandler(ByVal value As RoutedEventHandler)
      Me.AddHandler(UserControlBase.CloseTabItemRoutedEvent, value)
    End AddHandler

    RemoveHandler(ByVal value As RoutedEventHandler)
      Me.RemoveHandler(UserControlBase.CloseTabItemRoutedEvent, value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, _
               ByVal e As System.Windows.RoutedEventArgs)
      Me.RaiseEvent(e)
    End RaiseEvent
  End Event

  Protected Overridable Sub OnCloseTabItem()

    RaiseEvent CloseTabItem(Me, _
            New RoutedEventArgs(CloseTabItemRoutedEvent, Me))

  End Sub

  ''' <summary>
  '''     This is always true so it is implement here.
  ''' </summary>
  Protected Overridable Sub CloseTabItemCanExecute( _
      ByVal sender As Object, _
      ByVal e As CanExecuteRoutedEventArgs)

    e.CanExecute = True

  End Sub

  ''' <summary>
  ''' This must be overriden in deriving classes.
  ''' Making a base UserControl class MustInherit causes the Visual Studio designer
  ''' to blow up.  There are workarounds but I don't like them.
  ''' This is just easier and it works great.
  ''' </summary>
  Protected Overridable Sub CloseTabItemExecute( _
      ByVal sender As System.Object, _
      ByVal e As System.Windows.RoutedEventArgs)

    Throw New NotImplementedException("This method must be overriden")

  End Sub

End Class

The CloseTabItemExecute method must be overriden in deriving classes or the above execption will get thrown if the method is executed.  Please read the comments above the method to understand why the UserControlBase is not an abstract or MustInherit class.

When the TabItem is ready to close itself, the deriving classes execute the OnCloseTabItem method and the CloseTabItemRoutedEvent is rasised.

The CloseTabItemRoutedEvent is handled in the application Window.  You can also abstract some of the below TabControl code into a derived TabControl class.  For this demo, I’m keeping here and simple.

Class Window1

  Private Sub Close_Executed(ByVal sender As System.Object, _
                             ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
    Me.Close()

  End Sub

  Private Sub CloseTabHandler(ByVal sender As Object, _
                              ByVal e As RoutedEventArgs)
    Me.tcApplicationForms.Items.Remove(CType(sender, UserControlBase).Parent)

  End Sub

  Private Sub CommandBinding_CanExecute(ByVal sender As System.Object, _
                                        ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)
    e.CanExecute = True

  End Sub

  Private Sub DisplayTabItem(ByVal strHeaderText As String)

    Dim ti As TabItem = GetTabItemByHeaderString(strHeaderText)

    If ti Is Nothing Then
      ti = New TabItem
      ti.Header = strHeaderText

      If mnuUseBetterWay.IsChecked = True Then
        ti.Content = New BetterWay With {.Name = strHeaderText}

      Else
        ti.Content = New NotSoGoodWay With {.Name = strHeaderText}
      End If

      Me.tcApplicationForms.Items.Add(ti)
    End If

    Me.tcApplicationForms.SelectedItem = ti
    ti.Focus()

  End Sub

  Private Function GetTabItemByHeaderString(ByVal strHeaderText As String) As TabItem

    For Each ti As TabItem In Me.tcApplicationForms.Items

      If ti.Header = strHeaderText Then
        Me.tcApplicationForms.SelectedItem = ti
        Return ti
      End If

    Next

    Return Nothing

  End Function

  Private Sub Open_Executed(ByVal sender As System.Object, _
                            ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
    DisplayTabItem(e.Parameter.ToString)

  End Sub

  Private Sub Window1_Loaded(ByVal sender As Object, _
                             ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    
    EventManager.RegisterClassHandler(GetType(UserControlBase), _
        UserControlBase.CloseTabItemRoutedEvent, New RoutedEventHandler(AddressOf CloseTabHandler))

  End Sub

End Class

In a “real world” application the main difference will be how the application decides which form to load in the DisplayTabItem code.  In my applications the MenuItem.CommandParamenter has the assembly and class of the UserControl to load.  I then use reflection to load the UserControl and then add it to the TabItem like I am above.  There are several other considerations for TabItem MDI that I will get into great detail when I write that article for the WPF Business Application Series.

The DisplayTabItem method is multi-purpose in nature.  If the TabItem is not loaded DisplayTabItem loads it.  If the TabItem it is loaded DisplayTabItem it brings it into focus.

The CloseTabHandler removes the TabItem from the TabControl.Items collection.

The CommandBinding_CanExecute method set the CanExecuteRoutedEventArgs.CanExecute property to True since the two commands are always available.

Source Download

This Sample code is a Visual Studio 2008 solution.  This code will run under Visual Studio 2005 but will require a little work on your part.  Create a new VS 2005 WPF solution.  Then import all the code and XAML files into the new solution and your up and running.

After downloading please change the extension form .doc to .zip.  This is a requirement of WordPress.com

WPF TabControl MDI and CommandBindings Source

Closing

I hope that you can learn just a little bit more about WPF fom this article and the Sample Series.

Have a great day!

Just a grain of sand on the worlds beaches.


WPF Business Application Series Part 2 – Form Notification Control That Binds To IDataErrorInfo.Error Property

February 23, 2008

Form Notification Control

I have posted the second article in the WPF Business Application Series.  You can read Part 2 on Code Project here: Form Notification Control That Binds To IDataErrorInfo.Error Property

This article covers in great detail the Form Notification control that binds to the IDataErrorInfo.Error property.  Binding to a business entity object that implements the IDataErrorInfo interface is also covered.

Article Special Features

  • Shows how the Form Notification popup is rendered in the Adorner Layer.
  • Explains how to extend the data binding system to utilize logical operations in trigger conditions.
  • Explains how to get around the ToolBar Button and TextBox IDataErrorInfo data binding gotcha. 
    • Problem is the TextBox source is not updated when a ToolBar Button is clicked.  A solution is provided.
  • Shows how to persist validation error cues in the Adorner layer when switching between TabItems.
  • Explains how to use the Dispatcher object to permit the System.Timers.Timer thread to update the UI.
  • Shows a creative use of skin ControlTempates to provide rounded corners on TextBoxes in one of the skins.

Metallic Skin

I hope you can learn about using WPF for writing business applications from this series.

Have a great day!

Just a grain of sand on the worlds beaches.


Major Update To: WPF Business Application Series Part 1 of n – Application Structure, Skinning & Custom ToolBar Button Control

February 21, 2008

I have rewritten the WPF Business Application Series Part 1 of n – Application Structure, Skinning & Custom ToolBar Button Control article.

You can read the updated article on Code Project here: Application Structure, Skinning & Custom ToolBar Button Control.

After the first article was posted, I received feedback and spoke with Josh Smith about styling.  We also discussed where we felt the industry could be going with respect to packaged theme packs.  After analyzing this feedback I sat down and completely rewrote the section on skinning.

I have added an additional skin and a TimeDisplay control for the StatusBar.

This article also exposes a glitch with the TabControl when switching skins.  If your application depends on the TabItem triggers that test for the TabStripPlacement, you will need to assign the TabControl’s, TabStripPlacement property in the TabControl’s XAML markup and not the style.  If you don’t, when the skin is swapped at run-time, those triggers won’t fire and you UI will not look correct.

I hope you can learn about using WPF for writing business applications from this series.

Have a great day!

Just a grain of sand on the worlds beaches.


WPF Validation Errors Disappear Inside TabControl When Switching TabItems

February 19, 2008

I have been working with the new WPF 3.5 IDataErrorInfo interface and data binding to business objects.

My window is made up of a TabControl with several TabItems that have TextBoxes that are data bound to business objects that implement the IDataErrorInfo interface.

The problem I was having was that when I switched TabItems and came back to the TabItem with the displayed entry errors, the Validation.Error cues would disappear.

I did a lot of searching and could not find a solution.  I even read a bug report about this at: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=295933

I figured out this was not a data binding problem.  The issue is, the Validation.Error cues are painted in the Adorner Layer.  When you switch tabs, that layer is discarded.

So I sent Josh Smith an email for suggestion.  He knew the answer (of course!).

Solution

You must wrap the content of the TabItem in an AdornerDecorator and your problem is solved.

The below code shows the Border control I have placed as the first child of the TabItem.  Inside the StackPanel are the TextBoxes, etc.

    <Border>
      <AdornerDecorator>
        <StackPanel>
        ...
       </StackPanel>
      </AdornerDecorator>
    </Border>

You can see the * which is my Validation.ErrorTemplate rendering.  Using the above AdornerDecorator the user can switch between TabItems and not loose the Validation.Error cues that are painted in the Adorner Layer.

tabcontroladornerfix

Have a great day,

Just a grain of sand on the worlds beaches.


Visual Studio 2008 Tip: Auto Complete & List Members

February 17, 2008

When working in Visual Studio 2008 with Auto Complete turned on, as you type variable or Type names, the Auto Complete feature begins by displaying either the Common or All list as in the picture below.  You have probably noticed that he text in the Auto Complete list is larger that the code text.  Check out this blog post Visual Studio 2008 Tip View Debugging Information Easier to learn how to do this.

 Auto Complete List Expanded

As you continue to type, the listing now collapses as in the picture below. 

Tip:  When in the collapsed view you can return to the expanded view by pressing CTRL + J.

Auto Complete List Collapsed

Tip: When the full listing is visible, but you would like to view the code that is covered by the Intellisense pop-up, press and hold the CTRL key, and the pop-up sets its opacity to 10% so that you can view the code that is below the pop-up.

Auto Complete List Transparent

Have a great day,

Just a grain of sand on the worlds beaches.


After Installing The New Framework 3.5 SDK VS2008 XAML Intellisense No Longer Works – Workaround Posted

February 16, 2008

I had blogged about a problem with the new Framework 3.5 SDK installation here .  After performing the installation the XAML code editor Intellisense quit working.

I also posted this issue on the SDK Team’s blog.  Within an hour of posting on their blog the SDK Team has sprung into action and began working on this issue.

I want to thank Microsoft employees Karin Meier-Magruder, Sean Grimaldi, Chris Hubbard and the rest of the developers who quickly got this issue sorted out and have posted a fix for the problem that you can read here: http://blogs.msdn.com/windowssdk/comments/7850578.aspx

This very simple workaround involves registering one dll using regsvr32.  You do not need to repair your Visual Studio 2008 installation.

Have a great day,

Just a grain of sand on the worlds beaches.


Visual Studio 2008 Tip: Fast Window Switching; Ctrl + Tab

February 15, 2008

Visual Studio 2008 Fast Window Switching

When working on Visual Studio projects, I find that sometimes I have a number of code and .xaml files opened.  I then use the overflow feature of the tabbed interface to locate the code window I need to edit.

There is another way.  CTRL + TAB.  This key stroke combination will bring up the above window.  I lists your ToolWindows and all code windows.  There is also a preview of the  selected window.

After the window opens, continue to hold the CTRL key.  Each time you press the TAB key, the window will cycle to the next window. 

You also use your arrow keys or mouse to navigate around the window and select the window you want to go.

Microsoft, nice feature!

Have a great day,

Just a grain of sand on the worlds beaches.


Follow

Get every new post delivered to your Inbox.

Join 247 other followers