In-Depth

Creating a Custom Start Page in Visual Studio 2017

Visual Studio is all about making the developer coding experience more streamlined, allowing you to get to elements of the IDE that matter to you most. Customizing the Start Page is, well, a good start.

Visual Studio lets you craft tools that run in a variety of contexts. Naturally, you can create desktop applications for Windows systems. Azure- and self-hosted Web services are another option. And for those who don't want to venture that far, you can create code libraries that run right inside of Visual Studio itself.

In this article, we'll replace the Microsoft-supplied Visual Studio 2017 Start Page with one crafted to meet your corporate needs. Figure 1 shows the goal -- a panel that lists assigned projects with names, descriptions, date and progress indicators, and a convenient button that will have you working on delegated projects in no time.

The Start Page isn’t actually an application. Instead, it's a XAML file that optionally references code hosted in associated assemblies. The presentation developed in this article consists of a replacement XAML Start Page, plus a Windows Presentation Foundation (WPF)-based helper assembly that retrieves and displays the assigned projects.

[Click on image for larger view.] Figure 1. Customizing the Visual Studio Start Page

Before delving into the code, a quick word of caution: This sample was written using Visual Studio 2017 Release Candidate; therefore, it might be subject to last-minute platform changes by Microsoft.

Start Visual Studio 2017, and create a new project that will build the data, and present it in a XAML user control. For the new project, select the C# WPF User Control Library project template, and name the project TrackerStartPageControl. Save the project, selecting the Create Directory for Solution field in the Save Project window, and naming the containing solution TrackerStartPage.

Because the running user control will interact with Visual Studio itself, you need to add a reference to the Visual Studio interaction library. In the Solution Explorer panel, right-click on the References branch and select Add Reference from the menu. On the Reference Manager Assemblies panel, search for and select the Microsoft.VisualStudio.Shell.Framework assembly. It will have a version number of 15.0.0.0. Click OK to add it to the project.

As with most projects, the new Start Page begins with data, specifically the collection of assigned projects. Add a new class file to the project named ProjectItem.cs. Its members expose the data elements needed for the display: project name, description, status and so on. Listing 1 documents these members, including a few that gently massage the data for stability and ease of use later.

Listing 1: The ProjectItem Class
class ProjectItem
{
  private int PercentComplete = 0;

  public string Name { get; set; }
  public string Description { get; set; }
  public string VSCommand { get; set; }
  public DateTime Assigned { get; set; }
  public DateTime Due { get; set; }
  public int Progress
  {
    get
    {
      return this.PercentComplete;
    }
    set
    {
      this.PercentComplete = Math.Max(0, Math.Min(100, value));
    }
  }
  public bool IsOverdue
  {
    get
    {
      return (DateTime.Today > this.Due.Date);
    }
  }
}

Now it's time to configure that user control. Rename the UserControl1.xaml file to ProjectList.xaml. Open its codebehind file, ProjectLists.xaml.cs. If the class it contains is still named UserControl1, rename it to ProjectList, ensuring that its constructor is updated, as well:

public partial class ProjectList : UserControl
{
  public ProjectList()
  {
    InitializeComponent();
  }
}

The ProjectList class marries the underlying ProjectItem data with its XAML presentation. In a real-world project, a fancy block of code would retrieve the assignment list from a database, using some amazing technology like the Entity Framework, no doubt. For this sample, pretend data is sufficient. Listing 2 adds the GetProjectList method to the ProjectList class, a function that returns a small set of assignment records.

Listing 2: The GetProjectList Method
private static List<ProjectItem> GetProjectList()
{
  return new List<ProjectItem>
  {
    new ProjectItem {
      Name = "Customer Status Report",
      Description = "Support the new 'Review' status code, " +
        "and add the customers address to the report output.",
      VSCommand = "File.OpenProject " +
        @"C:\WorkArea\CustomerReport\CustomerReport.csproj",
      Assigned = DateTime.Today.AddDays(-7),
      Due = DateTime.Today.AddDays(14),
      Progress = 30
    },
    new ProjectItem {
      Name = "New Employee Processing",
      Description = "Set up a new employee account in the email system.",
      VSCommand = "File.OpenProject " +
        @"C:\WorkArea\ProcessEmployee\ProcessEmployee.csproj",
      Assigned = DateTime.Today.AddDays(-20),
      Due = DateTime.Today.AddDays(-3),  // Overdue
      Progress = 90
    }
  };
}

If you look back at Figure 1, you'll see Open buttons for each project entry. A simple event handler retrieves the necessary project-open command from the button's Tag property (originally sourced from each item's VSCommand property) and executes it using features from the Framework Shell library added earlier:

private void OpenProjectButton_Click(object sender, RoutedEventArgs e)
{
  string theCommand = (string)((Button)sender).Tag;
  Microsoft.VisualStudio.Shell.VSCommands.ExecuteCommand.Execute(theCommand, null);
}

That's most of the C# code. Switch over to the XAML content, in the ProjectList.xaml file. It's fairly barren now, just the supporting structure for an empty WPF user control:

<UserControl x:Class="TrackerStartPageControl.ProjectList"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
  xmlns:local="clr-namespace:TrackerStartPageControl"
  mc:Ignorable="d" 
  d:DesignHeight="300" d:DesignWidth="300">
  <Grid>
  </Grid>
</UserControl>

Listing 3 presents the XAML content that appears within the <Grid> tag. While the specifics of this block are outside of this article's focus, in short, the markup binds members of the ProjectItem class to attributes (properties) from WPF controls (including TextBlock, ProgressBar and Button). Most of the tags and attributes exist to make the data look pretty. Also notice how the VSCommand property is bound to a Button.Tag attribute, a relationship that came in handy in the previously added button event handler.

Listing 3: XAML for ProjectList Class
<ItemsControl Name="UserProjects">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <StackPanel>
        <TextBlock FontFamily="Segoe UI" FontSize="18" Foreground="RoyalBlue"
          FontWeight="Bold" Text="{Binding Name}" />
        <TextBlock FontFamily="Segoe UI" FontSize="12" Foreground="Gray"
          TextWrapping="Wrap" MaxHeight="36pt" Text="{Binding Description}" />
        <Grid Grid.Column="1" Grid.Row="1">
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" MaxWidth="400"/>
          </Grid.ColumnDefinitions>
          <ProgressBar Grid.Column="0" Height="12" Margin="0,5"
            IsIndeterminate="False" Foreground="LightGreen"
            Background="Gainsboro" Value="{Binding Progress}" />
        </Grid>
        <TextBlock FontFamily="Segoe UI" FontSize="12" Foreground="Gray">
          <TextBlock Text="{Binding Assigned, StringFormat='Assigned {0:M/d/yyyy}'}" />
          •
          <TextBlock Text="{Binding Due, StringFormat='Due {0:M/d/yyyy}'}">
            <TextBlock.Style>
              <Style TargetType="TextBlock">
                <Setter Property="Foreground" Value="Gray" />
                <Style.Triggers>
                  <DataTrigger Binding="{Binding IsOverdue}" Value="True">
                    <Setter Property="Foreground" Value="Red"/>
                  </DataTrigger>
                </Style.Triggers>
              </Style>
            </TextBlock.Style>
          </TextBlock>
        </TextBlock>
        <Button HorizontalAlignment="Left" Margin="0,5,0,15"
          Content="Open" Width="100" Tag="{Binding VSCommand}"
          Click="OpenProjectButton_Click" />
      </StackPanel>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

The outermost control from Listing 3 is named UserProjects. This ItemsControl instance transforms the ProjectItem data into beautiful output, assuming that it has access to that data in the first place. Return to the codebehind (the ProjectList.xaml.cs file), and link the data and XAML together by adding an assignment in the class constructor, just after the call to InitializeComponent:

UserProjects.ItemsSource = GetProjectList();

That's it for the user control. Nothing encountered so far has been specific to Start Page development; the ProjectList user control could easily have appeared in any WPF application. But because the Start Page is a plain XAML file without its own codebehind logic, deferring most of the work to a user control was a necessary step. And while all that’s needed from now on is a simple XAML file, adding a secondary WPF project will simplify development and provide a literal window into the look and feel of the Start Page.

Add a new C# WPF User Control Library project to the solution, calling this new project TrackerStartPage. Once added, right-click on the project in the Solution Explorer panel and choose Set as StartUp Project from the menu that appears. As with the earlier user control project, add a reference to the Microsoft.VisualStudio.Shell.Framework assembly. Also, add a reference to the TrackerStartPageControl project through the Projects area of that same Reference Manager window.

While still hanging around the Solution Explorer, rename the UserControl1.xaml file to TrackerPage.xaml. Open the file, and make several changes to the XAML content: remove the x:Class attribute, increase the DesignHeight and DesignWidth attributes from 300 to 500, add new vsfx and trk namespace attributes, and change the existing <Grid> tag to <StackPanel> instead:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
  xmlns:local="clr-namespace:TrackerStartPage"
  xmlns:vsfx="clr-namespace:Microsoft.VisualStudio.Shell;
    assembly=Microsoft.VisualStudio.Shell.Framework"  
  xmlns:trk="clr-namespace:TrackerStartPageControl;
    assembly=TrackerStartPageControl"
  mc:Ignorable="d" d:DesignHeight="500" d:DesignWidth="500">
  <StackPanel>
  </StackPanel>
</UserControl>

The removal of x:Class is a necessary change for Start Page XAML files, but most of the other changes were for development convenience or data-binding purposes. Within that new <StackPanel> tag, add new XAML content that will display section labels ("Your Projects" and "Other Projects"), provide links to the built-in Visual Studio new-project and open-project features (the two Hyperlink tags), and most important, activate the previously designed user control (the trk:ProjectList element):

<Label Content="Your Projects" FontFamily="Segoe UI" FontSize="26"
  FontWeight="Bold" Foreground="White" Background="CornflowerBlue" />
<trk:ProjectList Margin="20,10"/>
<Label Content="Other Projects" FontFamily="Segoe UI" FontSize="26"
  FontWeight="Bold" Foreground="White" Background="CornflowerBlue" />
<TextBlock Margin="20,5" FontFamily="Segoe UI" FontSize="16">
  <Hyperlink Command="{x:Static vsfx:VSCommands.ExecuteCommand}"
    CommandParameter="File.NewProject">New Project...</Hyperlink></TextBlock>
<TextBlock Margin="20,5" FontFamily="Segoe UI" FontSize="16">
  <Hyperlink Command="{x:Static vsfx:VSCommands.ExecuteCommand}"
    CommandParameter="File.OpenProject">Open Project...</Hyperlink></TextBlock>

The last change is a bit of cleanup. Open the codebehind for this file, the TrackerPage.xaml.cs file, and if you see a constructor method, remove it. Build the solution. If you return to the main TrackerPage.xaml file, you should see a preview of the Start Page content.

It's time to deploy the Start Page files. Only two files need to move: the just-built TrackerPage.xaml file, and the TrackerStartPageControl.dll user control assembly. Copy the XAML file to the following folder (or its variant on your system, if your configuration isn’t exactly the same):

%USERPROFILE%\My Documents\Visual Studio 2017\StartPages\

The DLL assembly goes in the shared private assembly area for Visual Studio:

%PROGRAMFILES(X86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\PrivateAssemblies

Because an errant Start Page could ruin your day, it's best to test the page within an experimental Visual Studio session. From the Windows Start button, open the Developer Command Prompt for Visual Studio 2017. When the command window appears, enter the command that initiates the experimental instance:

devenv /rootsuffix Exp

Within this Visual Studio instance, access the Tools | Options command. When the Options window appears, select Environment and then Startup within the hierarchy. On that Startup panel, select Show Start Page in the Set At Startup field, and choose the element that ends in TrackerPage.xaml from the Customize Start Page field. Click OK to save your changes.

To view the new start page, select the File | Start Page command. If the page is to your liking, close down the experimental version of Visual Studio, start up the application normally, and then run through the same configuration steps in the Options window. And then get to work on those assigned projects. The changes for the New Employee Processing app are overdue!

About the Author

Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.

comments powered by Disqus

Featured

Subscribe on YouTube