65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF Metro:Windows 8 开始屏幕“克隆”

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (55投票s)

2011年9月20日

CPOL

3分钟阅读

viewsIcon

160355

downloadIcon

16327

一个复制 Windows 8 开始屏幕的 WPF 应用程序

WpfMetro/Screenshot_1.png

WpfMetro/Screenshot_2.png

WpfMetro/WPF_Metro_Blue.png

引言

在观看了一些 BUILD2011 视频后,我不禁想知道是否有可能在 WPF 中复制 Win 8 的开始屏幕。示例应用程序WPF Metro,显示磁贴,允许用户访问 Win XP 或 Win 7 开始菜单中可用的应用程序。 它是 Win 8 开始屏幕的一个简化版本。

要求

要查看源文件并运行演示,您需要

  • 至少 15 英寸的屏幕空间
  • Visual Studio 2010

WPF Metro

当您运行WPF Metro时,您将看到一系列磁贴。 双击一个磁贴以打开相关的应用程序。

要更改应用程序的皮肤,请将光标移动到应用程序的底部边缘,以激活“边缘 UI”,然后单击其中一个彩色按钮。

WpfMetro/Color_Buttons.png

快速跳转

如果您不想通过滑动来平移到磁贴组,您可以按一个键将特定的磁贴组平移到视图中,例如,要将“M”磁贴组平移到视图中,请按 M 键。

设计和布局

我用 Expression Blend 设计了 WPF Metro。 主要的布局容器是 MainCanvasMetroStackPanel

WpfMetro/Layout.png

MetroStackPanelMainCanvas 的子元素。 磁贴被添加到 WrapPanel,然后添加到 MetroStackPanelWPF Metro 中的一个磁贴是一个名为 TileUserControl,它由一个 TextBlock 和一个 Image 控件组成,用于显示应用程序的图标。

WpfMetro/Tile.png

代码

为了显示相关的磁贴,WPF Metro 提取 Start 菜单的 Programs 文件夹中的快捷方式链接到的 .exe 文件的图标。 然后通过将 Tile 控件添加到 WrapPanel(最终添加到 MetroStackPanel)中,按字母顺序显示这些磁贴。

Tile UserControl 的代码如下所示

Partial Public Class Tile

    Public Property ExecutablePath() As String

    Private Sub Tile_PreviewMouseDoubleClick(ByVal sender As Object, _
                                 ByVal e As System.Windows.Input.MouseButtonEventArgs) _
                                 Handles Me.PreviewMouseDoubleClick
        Try
            Process.Start(ExecutablePath)
        Catch ex As Win32Exception
            Exit Sub
        End Try
    End Sub
End Class

MainWindow Initialized 事件处理程序中,我们执行以下操作:

Private Sub MainWindow_Initialized(ByVal sender As Object, _
                               ByVal e As System.EventArgs) Handles Me.Initialized
    timer = New DispatcherTimer
    anim = New DoubleAnimationUsingKeyFrames

    anim.Duration = TimeSpan.FromMilliseconds(1800)
    timer.Interval = New TimeSpan(0, 0, 0, 0, 1000)
    AddHandler timer.Tick, AddressOf timer_Tick
End Sub

MainWindow Loaded 事件处理程序中,我们设置应用程序的皮肤并显示磁贴

Private Sub MainWindow_Loaded(ByVal sender As Object, _
                     ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    Dim metro As New Metrolizer
    metro.DisplayTiles(MetroStackPanel)

    Dim path As String = My.Settings.SkinPath
    Dim newDictionary As New ResourceDictionary()
    newDictionary.Source = New Uri(path, UriKind.Relative)
    Me.Resources.MergedDictionaries.Clear()
    Me.Resources.MergedDictionaries.Add(newDictionary)
End Sub

包含 DisplayTiles() 方法的 Metrolizer 类的代码如下所示

Imports System.Collections.Generic

Public Class Metrolizer

    Private wrapPanelX As Double = 0

    Public Sub DisplayTiles(ByRef metroStackPanel As StackPanel)
        Dim alphabet() As String = {"a", "b", "c", "d", "e", "f", "g", "h", "i", _
                                    "j", "k", "l", "m", "n", "o", "p", "q", "r", _
                                    "s", "t", "u", "v", "w", "x", "y", "z"}
        Dim numbers() As String = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}
        Dim di As Dictionary(Of String, String()) = New IconsAndPaths().GetIconsAndPaths()

        For Each s As String In alphabet
            Dim letter As String = s
            Dim coll = di.Where(Function(k) k.Key.StartsWith(letter, True, Nothing))
            If (coll.Count > 0) Then
                AddTiles(coll, metroStackPanel, letter)
            End If
        Next

        For Each s As String In numbers
            Dim letter As String = s
            Dim coll = di.Where(Function(k) k.Key.StartsWith(letter, True, Nothing))
            If (coll.Count > 0) Then
                AddTiles(coll, metroStackPanel, letter)
            End If
        Next
    End Sub

    Private Sub AddTiles(ByVal coll As IEnumerable_
			(Of KeyValuePair(Of String, String())), _
                         	ByRef metroStackPanel As StackPanel, ByVal letter As String)
        Dim tileWrapPanel As New WrapPanel
        tileWrapPanel.Orientation = Orientation.Vertical
        tileWrapPanel.Margin = New Thickness(0, 0, 20, 0)
        ' 3 tiles height-wise
        tileWrapPanel.Height = (110 * 3) + (6 * 3)

        For Each kvp As KeyValuePair(Of String, String()) In coll
            Dim newTile As New Tile
            newTile.ExecutablePath = kvp.Value(1)
            newTile.TileIcon.Source = New BitmapImage_
		(New Uri(kvp.Value(0), UriKind.Absolute))
            newTile.TileTxtBlck.Text = kvp.Key
            newTile.Margin = New Thickness(0, 0, 6, 6)
            tileWrapPanel.Children.Add(newTile)
        Next

        WrapPanelLocation(letter, tileWrapPanel)
        metroStackPanel.Children.Add(tileWrapPanel)
    End Sub

    ''' <summary>
    ''' Determines the probable location of a WrapPanel that is added
    ''' to MetroStackPanel (assuming that MetroStackPanel was
    ''' like a Canvas).
    ''' </summary>
    ''' <param name="letter">The alphabetical letter representing a WrapPanel group
    ''' in MetroStackPanel.</param>
    ''' <param name="tileWrapPanel">The WrapPanel that was added to MetroStackPanel.
    ''' </param>
    ''' <remarks></remarks>
    Private Sub WrapPanelLocation(ByVal letter As String, _
			ByVal tileWrapPanel As WrapPanel)
        If (WrapPanelDi.Count = 0) Then
            WrapPanelDi.Add(letter, 0)
        Else
            WrapPanelDi.Add(letter, wrapPanelX)
        End If

        ' Increase value of wrapPanelX as appropriate. 
        ' 6 is right margin of a Tile.
        If (tileWrapPanel.Children.Count <= 3) Then
            wrapPanelX += ((110 + 6) + 18)
        Else
            Dim numberOfColumns As Double = _
		Math.Ceiling(tileWrapPanel.Children.Count / 3)
            Dim x As Double = (numberOfColumns * 110) + (numberOfColumns * 6) + 18
            wrapPanelX += x
        End If
    End Sub
End Class

IconsAndPaths 中的 GetIconsAndPaths() 方法查找 .lnk 文件链接到的 .exe 文件的位置,并提取它们的图标。 IconsAndPaths 类的代码如下所示

Imports System.IO
Imports System.Drawing
Imports IWshRuntimeLibrary

Public Class IconsAndPaths
    Public IconsPathsDi As New Dictionary(Of String, String())

    Public Function GetIconsAndpaths() As Dictionary(Of String, String())
        Dim path As String = Environment.GetFolderPath_
		(Environment.SpecialFolder.CommonStartMenu) & _
                            "\Programs"
        Dim startMenuProgDir As New DirectoryInfo(path)

        If (startMenuProgDir.Exists <> True) Then
            Dim dirPath As String = Environment.GetFolderPath_
				(Environment.SpecialFolder.StartMenu) & _
                                    "\Programs"
            startMenuProgDir = New DirectoryInfo(dirPath)
        End If
        Dim shell As New WshShell

        CreateIconsDirectory()

        For Each fi As FileInfo In startMenuProgDir.GetFiles
            If (fi.Extension = ".lnk") Then
                ' The length of the file's name alone minus .lnk
                Dim nameLength As Integer = fi.Name.Length - 4
                ' Name to display in UserControl
                Dim displayName As String = fi.Name.Substring(0, nameLength)
                ' Copy of shortcut
                Dim link As IWshShortcut = CType(shell.CreateShortcut(fi.FullName),  _
                                                IWshShortcut)

                Dim potentialExePath As String = link.TargetPath
                Dim potentialExe As New FileInfo(potentialExePath)

                If (potentialExe.Extension = ".exe") Then
                    Dim tileIconPath As String = Environment.CurrentDirectory & _
                                                "\WPF Metro Icons\" & _
                                                displayName & ".png"
                    Try
                        Dim ico As System.Drawing.Icon = _
                            System.Drawing.Icon.ExtractAssociatedIcon(potentialExePath)
                        ico.ToBitmap().Save(tileIconPath, Imaging.ImageFormat.Png)

                        AddToDictionary(displayName, tileIconPath, potentialExePath)
                    Catch ex As FileNotFoundException
                        Exit Try
                    End Try

                End If
            End If
        Next

        ' Get icons for .lnk in ...Start Menu\Programs\...
        For Each di As DirectoryInfo In startMenuProgDir.GetDirectories()
            For Each fi As FileInfo In di.GetFiles()
                If (fi.Extension = ".lnk") Then
                    ' The length of the file's name alone minus .lnk
                    Dim nameLength As Integer = fi.Name.Length - 4
                    ' Name to display in UserControl
                    Dim displayName As String = fi.Name.Substring(0, nameLength)
                    ' Avoid install and uninstall files
                    If (displayName.Contains("install") <> True) Then
                        Dim link As IWshShortcut = CType(shell.CreateShortcut_
						(fi.FullName),  _
                                                        IWshShortcut)
                        Dim potentialExePath As String = link.TargetPath

                        If (potentialExePath.Contains(".exe")) Then
                            Dim tileIconPath As String = _
				Environment.CurrentDirectory & _
                                                "\WPF Metro Icons\" & _
                                                displayName & ".png"
                            Try
                                Dim ico As Icon = _
				Icon.ExtractAssociatedIcon(potentialExePath)
                                ico.ToBitmap().Save(tileIconPath, _
				Imaging.ImageFormat.Png)

                                CheckMSOfficeApps(displayName, tileIconPath)

                                AddToDictionary(displayName, tileIconPath, _
						potentialExePath)
                                
                            Catch ex As FileNotFoundException
                                Exit Try
                            Catch ex As ArgumentException
                                Exit Try
                            End Try

                        End If
                    End If
                End If
            Next
        Next

        Return IconsPathsDi
    End Function

    Private Sub AddToDictionary(ByVal displayName As String, _
				ByVal tileIconPath As String, _
                               ByVal exePath As String)
        If Not IconsPathsDi.ContainsKey(displayName) Then
            IconsPathsDi.Add(displayName, New String() {tileIconPath, exePath})
        End If
    End Sub

    Private Sub CheckMSOfficeApps(ByVal app As String, ByVal tileIconPath As String)
        If (app.Contains("Microsoft Office Access")) Then
            AddToDictionary(app, tileIconPath, "MSACCESS.EXE")
        ElseIf (app.Contains("Microsoft Office Excel")) Then
            AddToDictionary(app, tileIconPath, "EXCEL.EXE")
        ElseIf (app.Contains("Microsoft Office InfoPath")) Then
            AddToDictionary(app, tileIconPath, "INFOPATH.EXE")
        ElseIf (app.Contains("Microsoft Office OneNote")) Then
            AddToDictionary(app, tileIconPath, "ONENOTEM.EXE")
        ElseIf (app.Contains("Microsoft Office Outlook")) Then
            AddToDictionary(app, tileIconPath, "OUTLOOK.EXE")
        ElseIf (app.Contains("Microsoft Office PowerPoint")) Then
            AddToDictionary(app, tileIconPath, "POWERPNT.EXE")
        ElseIf (app.Contains("Microsoft Office Publisher")) Then
            AddToDictionary(app, tileIconPath, "MSPUB.EXE")
        ElseIf (app.Contains("Microsoft Office Word")) Then
            AddToDictionary(app, tileIconPath, "WINWORD.EXE")
        Else
            Exit Sub
        End If
    End Sub

    Private Sub CreateIconsDirectory()
        Dim dir As String = Environment.CurrentDirectory & "\WPF Metro Icons"
        If (Directory.Exists(dir)) Then
            Dim di As New DirectoryInfo(dir)
            For Each fi As FileInfo In di.GetFiles
                fi.Delete()
            Next
        Else
            Directory.CreateDirectory(dir)
        End If
    End Sub
End Class

磁贴的滚动是通过使用 MainCanvasPreviewMouseLeftButtonDownPreviewMouseLeftButtonUp 事件来实现的。

Private Sub MainCanvas_PreviewMouseLeftButtonDown(ByVal sender As Object, _
                                ByVal e As System.Windows.Input.MouseButtonEventArgs) _
                                Handles MainCanvas.PreviewMouseLeftButtonDown
    initMouseX = e.GetPosition(MainCanvas).X
    x = Canvas.GetLeft(MetroStackPanel)
End Sub

Private Sub MainCanvas_PreviewMouseLeftButtonUp(ByVal sender As Object, _
                                  ByVal e As System.Windows.Input.MouseButtonEventArgs) _
                                  Handles MainCanvas.PreviewMouseLeftButtonUp
    finalMouseX = e.GetPosition(MainCanvas).X
    Dim diff As Double = Math.Abs(finalMouseX - initMouseX)

    ' Make sure the diff is substantial so that tiles 
    ' don't scroll on double-click.
    If (diff > 5) Then
        If (finalMouseX < initMouseX) Then
            newX = x - (diff * 2)
        ElseIf (finalMouseX > initMouseX) Then
            newX = x + (diff * 2)
        End If

        anim.KeyFrames.Add(New SplineDoubleKeyFrame(newX, _
                                    KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1)), _
                                    New KeySpline(0.161, 0.079, 0.008, 1)))
        anim.FillBehavior = FillBehavior.HoldEnd
        MetroStackPanel.BeginAnimation(Canvas.LeftProperty, anim)
        anim.KeyFrames.Clear()
        timer.Start()
    End If
End Sub

DispatcherTimer 对象 timerTick 事件处理程序检查 StackPanel 是否超出视图,并将其恢复到合适的位置

' Check whether the StackPanel is no longer in view and
' return it to a suitable position.
Private Sub timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
    Dim mspWidth As Double = MetroStackPanel.ActualWidth

    If (newX > 200) Then
        anim.KeyFrames.Add(New SplineDoubleKeyFrame(45, _
                           KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1)), _
                           New KeySpline(0.161, 0.079, 0.008, 1)))
        anim.FillBehavior = FillBehavior.HoldEnd
        MetroStackPanel.BeginAnimation(Canvas.LeftProperty, anim)
        anim.KeyFrames.Clear()
    ElseIf ((newX + mspWidth) < 500) Then
        Dim widthX As Double = 500 - (newX + mspWidth)
        Dim shiftX As Double = newX + widthX
        anim.KeyFrames.Add(New SplineDoubleKeyFrame(shiftX, _
                           KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1)), _
                           New KeySpline(0.161, 0.079, 0.008, 1)))
        anim.FillBehavior = FillBehavior.HoldEnd
        MetroStackPanel.BeginAnimation(Canvas.LeftProperty, anim)
        anim.KeyFrames.Clear()
    End If
    timer.Stop()
End Sub

如果您对我是如何进行应用程序换肤的感兴趣,请参阅我的 WPF 换肤文章 这里

快速跳转

平移到特定的磁贴组是通过调用模块 QuickJumper 中定义的 ShiftStackPanel() 方法来完成的。

Imports System.Windows.Media.Animation

Module QuickJumper

    Public WrapPanelDi As New Dictionary(Of String, Double)

    ' Depending on which key was pressed moves MetroStackPanel so that
    ' the WrapPanel containing required tiles is in view.
    Public Sub ShiftStackPanel(ByRef letter As String, _
			ByRef metroStackPanel As StackPanel)
        If WrapPanelDi.ContainsKey(letter.ToLower()) Then
            Dim doubleAnim As New DoubleAnimationUsingKeyFrames()
            Dim newX As Double = WrapPanelDi(letter.ToLower())
            doubleAnim.Duration = TimeSpan.FromMilliseconds(1800)

            doubleAnim.KeyFrames.Add(New SplineDoubleKeyFrame(-newX, _
                                     KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1)), _
                                     New KeySpline(0.161, 0.079, 0.008, 1)))
            doubleAnim.FillBehavior = FillBehavior.HoldEnd
            metroStackPanel.BeginAnimation(Canvas.LeftProperty, doubleAnim)
            doubleAnim.KeyFrames.Clear()
        End If
    End Sub

End Module

键和值被添加到 Metrolizer 类中定义的 WrapPanelLocation() 方法中的 WrapPanelDi 中。

结论

我希望您从本文中获得了一些有用的东西。 我最初不喜欢 Metro UI,但我认为它非常适合 Win8,考虑到由于运行操作系统的设备上的屏幕空间很大,截断的文本较少。 我认为 Win8,或者他们最终称它为什么,最终发布时将会非常成功。

历史

  • 2011 年 9 月 20 日:首次发布
  • 2011 年 9 月 24 日:更新代码以打开 Microsoft Office 应用程序
  • 2011 年 9 月 29 日:添加快速跳转功能
© . All rights reserved.