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

Silverlight 文件管理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (37投票s)

2010 年 3 月 27 日

Ms-PL

12分钟阅读

viewsIcon

141803

downloadIcon

3508

使用视图模型样式模式创建简单的 Silverlight 4 文件管理器的实现

引言

该项目演示了使用模型视图样式模式实现一个简单的 Silverlight 文件管理器。该模式允许程序员创建一个完全没有用户界面的应用程序。程序员只需创建 ViewModelModel。然后,一位完全没有编程能力的设计师,可以从一张白纸开始,在Microsoft Expression Blend 4(或更高版本)中完全创建 View(用户界面)。

模型视图样式的强大之处

这个 Silverlight 项目并不是一个功能齐全的文件管理器,但它确实有效,并且希望能够展示一个非平凡的模型视图样式 Silverlight 项目示例。

然而,重点在于模型视图样式模式。我们将重点介绍以下重要方面:

  • 程序员创建包含集合、属性和命令(实现 ICommand)的代码。
  • 设计师使用 Expression Blend,无需编写任何代码即可创建完整的用户界面。

入门解决方案

我们将从 MVMFileManager_BaseStartProject.zip 文件中的解决方案开始。这包括一个入门级的 Silverlight 项目和一个标准的 ASP.NET 网站,该网站显示 Files 文件夹中包含的文件和文件夹。请注意,此网站可以使用 PHP 等其他语言实现,Silverlight 应用程序仍然可以正常工作。

两个简单的类,SilverlightFolderSilverlightFile,用于保存 Web 服务在 ASP.NET 网站中返回的文件和文件夹集合。

namespace MVMFileManagerSite
{
    [Serializable]
    public class SilverlightFolder
    {
        private ObservableCollection<silverlightfolder> _SubFolders;

        public ObservableCollection<silverlightfolder> SubFolders
        {
            get
            {
                if (_SubFolders == null)
                {
                    _SubFolders = new ObservableCollection<silverlightfolder>();
                }
                return _SubFolders;
            }
            set
            {
                _SubFolders = value;
            }
        }

        public string FolderName { get; set; }
        public string FullPath { get; set; }
    }

    [Serializable]
    public class SilverlightFile
    {
        public string FileName { get; set; }
        public string FilePath { get; set; }
    }
}

如果我们右键单击 Webservice.asmx 文件,然后选择在浏览器中查看,我们可以看到 Web 方法。

GetLocalFolders 方法返回 Files 目录中的文件夹集合。

GetLocalFiles 方法需要一个文件夹名称作为参数,并返回文件名集合,以及一个文件下载链接(通过将文件夹名和文件名传递给 DownloadFile.aspx 文件)。

程序员 - 模型和模型视图

现在,我们将在 Expression Blend 4(或更高版本)中打开该解决方案,并创建 ModelViewModel。但是,首先,我们将添加一个简单的类来支持命令。命令将允许设计师在 ViewModel 中引发事件。

我们将使用 John Papa 在其博客“Silverlight 中的 5 个简单命令步骤”中发布的代码。

MVMFileManager 入门解决方案中的 MVMFileManager Silverlight 项目的Classes 文件夹上右键单击,然后选择添加新项…

添加一个名为 DelegateCommand.cs 的类,然后单击确定

用以下代码替换所有代码

using System.Windows.Input;
using System;

// From http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/
public class DelegateCommand : ICommand
{
    Func< object, bool > canExecute;
    Action< object > executeAction;
    bool canExecuteCache;

    public DelegateCommand(Action< object > executeAction, Func< object, bool > canExecute)
    {
        this.executeAction = executeAction;
        this.canExecute = canExecute;
    }

    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        bool temp = canExecute(parameter);

        if (canExecuteCache != temp)
        {
            canExecuteCache = temp;
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, new EventArgs());
            }
        }

        return canExecuteCache;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        executeAction(parameter);
    }

    #endregion
}

Models 文件夹中,添加一个名为 SilverlightFolders.cs 的类,并用以下代码替换现有代码:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ServiceModel;
using MVMFileManager.FileManager;
using MVMFileManager;

namespace MVVMFileManager
{
    public class SilverlightFolderAndFiles
    {
        MainViewModel _MainViewModel;

        #region GetWebserviceAddress
        private string GetWebserviceAddress()
        {
            string strXapFile = @"/ClientBin/MVMFileManager.xap";

            string strBaseWebAddress =
                App.Current.Host.Source.AbsoluteUri.Replace(strXapFile, "");

            return string.Format
                   (@"{0}/{1}", strBaseWebAddress, @"WebService/WebService.asmx");
        }
        #endregion

        #region GetFolders
        public void GetFolders(MainViewModel objMainViewModel)
        {
            // Set MainViewModel
            _MainViewModel = objMainViewModel;

            // Set up web service call
            WebServiceSoapClient objWebServiceSoapClient =
                new WebServiceSoapClient();

            EndpointAddress MyEndpointAddress = new
                EndpointAddress(GetWebserviceAddress());

            objWebServiceSoapClient.Endpoint.Address = MyEndpointAddress;

            // Call web method
            objWebServiceSoapClient.GetLocalFoldersCompleted +=
                new EventHandler< GetLocalFoldersCompletedEventArgs >
                                  (objWebServiceSoapClient_GetLocalFoldersCompleted);
            objWebServiceSoapClient.GetLocalFoldersAsync();
        }

        void objWebServiceSoapClient_GetLocalFoldersCompleted
             (object sender, GetLocalFoldersCompletedEventArgs e)
        {
            foreach (var item in e.Result)
            {
                _MainViewModel.SilverlightFolders.Add(item);
            }

            // Get the files for the first Folder
            if (e.Result != null)
            {
                GetFiles(_MainViewModel, e.Result[0]);
            }
        } 
        #endregion

        #region GetFiles
        public void GetFiles(MainViewModel objMainViewModel, 
                             SilverlightFolder objSilverlightFolder)
        {
            // Set MainViewModel
            _MainViewModel = objMainViewModel;

            // Set up web service call
            WebServiceSoapClient objWebServiceSoapClient =
                new WebServiceSoapClient();

            EndpointAddress MyEndpointAddress = new
                EndpointAddress(GetWebserviceAddress());

            objWebServiceSoapClient.Endpoint.Address = MyEndpointAddress;

            // Call web method
            objWebServiceSoapClient.GetLocalFilesCompleted += 
                new EventHandler<GetLocalFilesCompletedEventArgs>
                (<getlocalfilescompletedeventargs>
                  objWebServiceSoapClient_GetLocalFilesCompleted);
            objWebServiceSoapClient.GetLocalFilesAsync(objSilverlightFolder.FolderName);
        }

        void objWebServiceSoapClient_GetLocalFilesCompleted
                          (object sender, GetLocalFilesCompletedEventArgs e)
        {
            foreach (var item in e.Result)
            {
                _MainViewModel.SilverlightFiles.Add(item);
            }
        }
        #endregion
    }
}

这是一个调用 GetFoldersGetFiles Web 服务方法的类。有几点需要注意:

  • 这些方法接受 ViewModel 的实例(将在下一步创建)作为参数之一。
  • ViewModel 的实例存储在 _MainViewModel 私有变量中,供 Web 服务回调方法稍后使用。
  • 当异步方法返回时,它会填充 ViewModel 上的相应集合。

ViewModels 文件夹中,添加一个名为 MainViewModel.cs 的类,并用以下代码替换现有代码:

using System;
using System.ComponentModel;
using System.Windows.Input;
using System.Collections.ObjectModel;
using MVMFileManager.FileManager;

namespace MVVMFileManager
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public MainViewModel()
        {
            // Set the command property
            SetFilesCommand = new DelegateCommand(SetFiles, CanSetFiles);

            // Set default values
            SilverlightFolders = new ObservableCollection< SilverlightFolder >();
            SilverlightFiles = new ObservableCollection< SilverlightFile >();
            
            // Pass a reference of this class to the method to get the Folders
            SilverlightFolderAndFiles objSilverlightFolderAndFiles = 
                                          new SilverlightFolderAndFiles();
            objSilverlightFolderAndFiles.GetFolders(this);
        }

        #region Commanding
        public ICommand SetFilesCommand { get; set; }

        public void SetFiles(object param)
        {
            // Get the Folder selected
            SilverlightFolder objSilverlightFolder = (SilverlightFolder)param;

            // Clear the file list
            SilverlightFiles = new ObservableCollection< SilverlightFile >();

            // Pass a reference of this class to the method to get the Files
            SilverlightFolderAndFiles objSilverlightFolderAndFiles = 
                                                   new SilverlightFolderAndFiles();
            objSilverlightFolderAndFiles.GetFiles(this, objSilverlightFolder);
        }

        private bool CanSetFiles(object param)
        {
            return true;
        } 
        #endregion

        #region Folders
        private ObservableCollection< SilverlightFolder > _SilverlightFolders;
        public ObservableCollection< SilverlightFolder > SilverlightFolders
        {
            get { return _SilverlightFolders; }
            private set
            {
                if (SilverlightFolders == value)
                {
                    return;
                }

                _SilverlightFolders = value;
                this.NotifyPropertyChanged("SilverlightFolders");
            }
        } 
        #endregion

        #region Files
        private ObservableCollection< SilverlightFile > _SilverlightFiles;
        public ObservableCollection< SilverlightFile > SilverlightFiles
        {
            get { return _SilverlightFiles; }
            private set
            {
                if (SilverlightFiles == value)
                {
                    return;
                }

                _SilverlightFiles = value;
                this.NotifyPropertyChanged("SilverlightFiles");
            }
        }
        #endregion
    
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }
}

这是将由设计师使用的类。设计师实际上不需要看到这段代码,当设计师将此类设置为 UI 页面的 DataContext 时,它会显示在 Blend 的 Data 部分。

请注意,此类实现了 ObservableCollection,因此对此类中存储的值的更改将导致通知绑定到它的任何 UI 元素,使其能够自动更新。此类还实现了 INotifyPropertyChanged,以便属性也能为绑定到它们的任何 UI 元素提供自动更改通知。

SetFilesCommand 属性实现了一个 ICommand。UI 元素(如 Button,或本例中的 TreeView 的选中节点)可以绑定到此属性并调用 SetFiles 方法(该方法已在类构造函数中使用 DelegateCommand 注册)。

设计师 - 视图

程序员已离开!

我们终于来到了本文的真正重点。请允许我强调一下,Silverlight 中的模型视图样式模式允许我们创建一个应用程序,而无需指定任何用户界面。用户界面可以完全在 Blend 中创建,而无需编写任何代码。

为什么这很重要?因为程序员不再需要拖累设计师。程序员并非有意拖累任何人(毕竟,我也是程序员),但通常没有任何东西可以在没有程序员实现的情况下投入生产。

设计师现在可以负责已实现模型视图样式模式的项目,并创建新的创新用户界面,而无需经过程序员。现在我们已经“解除了设计师的束缚”,我们可以预见未来用户界面将取得许多进展。

现在,想象一下程序员已经创建了前面的文件。这个项目现在可以交给设计师来实际创建用户界面。万一您错过了,请允许我再说一遍,程序员已经离开了!作为设计师,您可以将此项目交付生产,而无需编写任何其他代码。

为了确保您已准备就绪,请从工具栏中选择项目,然后选择生成项目。项目应该可以无错误地生成。

项目窗口中双击 MainPage.xaml 文件以打开它。

这将打开文件。它目前是一个空白的 UI。

对象和时间线窗口中单击LayoutRoot。接下来,在属性窗口中,单击 DataContext 旁边的新建按钮。

选择 MainViewModel,然后单击确定。现在已设置 DataContextLayoutRoot 的所有子对象都将能够访问 DataContext 提供的数据和命令。

如果您单击数据选项卡……

…然后展开 MainViewModel(在数据上下文部分下)。您将看到 MainViewModel 类公开的集合和命令。设计师可以通过简单地将此窗口中的项拖放到设计图面上的控件上来与 ViewModel 进行交互。

文件管理器 UI

单击工具栏上的资产图标。

在搜索框中键入“Grid”。单击 Grid 并将其拖到设计图面上。

Grid 放置在设计图面上,并将其大小调整为填满大部分页面。将鼠标悬停在 Grid 的中心(在蓝色条上),将出现一条定位线。

单击鼠标会在 Grid 中创建一个分割,生成两个单元格。

单击资产按钮,搜索 ScrollViewer,然后将其拖到 Grid 的其中一个单元格中。

再次执行该操作,并将 ScrollViewer 拖到 Grid 的另一个单元格中。

在每个 ScrollViewer 的属性中,单击高级选项框…

然后重置值。

这将导致每个 ScrollViewer 完全填充其在 Grid 中的单元格。

对象和时间线窗口中单击每个 ScrollViewer,并将它们分别重命名为 FoldersFiles

搜索 TreeView 控件。

将其拖放到对象和时间线窗口中的 Folders ScrollViewer 中。

TreeView属性中,通过单击每个设置的设置为自动按钮,将宽度高度设置为自动

单击对象和时间线窗口中的LayoutRoot 以选中它。

数据选项卡中,单击并拖动 SilverlightFolders 集合到对象和时间线窗口。

将集合放在 TreeView 上。

项目窗口中,右键单击 MVMFileManagerSite 项目,并将其设置为启动项目。

另外,右键单击 Default.aspx 页面,并将其设置为启动

F5 键运行项目。您可能会看到一个警告消息,只需单击

项目将运行,文件夹也会显示,但它们的格式不正确。

注意,您会在“错误”窗口中看到一个错误。原因是 Blend 设计器正在尝试在设计器中显示示例数据,但它无法运行提供数据的 Web 服务。但是,正如您所见,它在运行时确实有效。您可以在代码中使用命令 (DesignerProperties.IsInDesignTool) 来确定代码是否从 Blend 调用,并提供设计器可以显示的示例数据。在此示例中,我没有实现这一点,因为我想专注于模型视图样式代码。

在 Blend 的对象和时间线窗口中,右键单击 TreeView 控件,然后选择编辑附加模板 > 编辑生成项(项模板) > 编辑当前

这将带您进入模板编辑模式。选择下面的 TextBlock(它是文件路径绑定的对象),右键单击它,然后选择删除以删除它。

项目窗口中,打开 MVMFileManager 项目中的Images 文件夹,然后单击 Folder.png 文件。

将其拖放到设计图面上的任意位置。

它也会出现在对象和时间线窗口中,单击它并将其拖到 TextBlock 的顶部。

单击对象和时间线窗口中的 StackPanel,然后在属性窗口中,将其方向设置为 Horizontal

单击对象和时间线窗口中的 TextBlock,然后在属性窗口中,将其左侧 Margin 设置为 5

单击对象和时间线窗口中的返回范围图标,以返回正常设计模式。

F5 键运行项目。文件夹看起来好多了。现在处理文件。

单击对象和时间线窗口中的LayoutRoot 以选中它。

数据选项卡中,单击并拖动 SilverlightFiles 集合到设计图面。

对象和时间线窗口中,它将出现在 Files ScrollViewer 下方,它需要位于ScrollViewer 内部。

对象和时间线窗口中,将其拖放到 Files ScrollViewer 内部。

ListBox属性中,通过单击每个设置的设置为自动按钮,将宽度高度设置为自动

按键盘上的F5 键运行项目。文件显示出来,但格式不正确。我们应该看到文件名,并且当我们单击它时,我们希望下载文件。我们可以通过使用 HyperlinkButton 控件来实现。

在 Blend 的对象和时间线窗口中,右键单击 ListBox 控件,然后选择编辑附加模板 > 编辑生成项(项模板) > 编辑当前

这将带您进入模板编辑模式。选择两个 TextBlock右键单击它们,然后选择删除以删除它们。

Assets中,搜索 HyperlinkButton

将其拖放到设计图面上。

HyperlinkButton属性中,单击 Content 属性的高级选项框。

选择数据绑定…

  • 选择数据上下文选项卡
  • 选择 FileName(在 SilverlightFiles 下)
  • 单击确定

HyperlinkButton属性中,单击 NavigateUri 属性的高级选项框。

选择数据绑定...

  • 选择数据上下文选项卡
  • 选择 FilePath(在 SilverlightFiles 下)
  • 单击确定

按键盘上的F5 键运行项目。第一个文件夹的文件将显示,并且当您单击文件时,它将下载。但是,如果您更改文件夹,文件将不会更改。

现在,如何显示所选文件夹的文件?

在 Blend 中,单击对象和时间线窗口中的返回范围图标,以返回正常设计模式。

单击工具窗口上的资产按钮。

在搜索框中键入“InvokeCommand”,然后将显示 InvokeCommandAction 行为。(如果您看不到此项,请安装 Silverlight 4 SDK)。将其拖放到 TreeView 控件上(在对象和时间线窗口或设计画布上)。

行为将显示在对象和时间线窗口的 TreeView 控件下方。

单击对象和时间线窗口中的行为,然后在属性窗口中,将 EventName 设置为 SelectedItemChanged(以便在 TreeView 中的选定项更改时触发行为)。

单击常用属性下的命令旁边的数据绑定图标。

  • 选择数据上下文选项卡
  • 选择 SetFilesCommand(在 MainViewModel 下)
  • 单击确定

这指示行为调用 ViewModel 中的 SetFilesCommand

单击常用属性下的CommandParameter旁边的高级选项框。

选择数据绑定…

  • 选择元素属性选项卡
  • 选择[TreeView](在场景元素窗口中)
  • 选择SelectedItem(在属性窗口中)
  • 单击确定

这指示行为将当前选中的 TreeView 项所绑定的 SilverlightFolder 对象传递给 ViewModel 中的 SetFilesCommand

按键盘上的F5 键编译并运行项目。TreeView 控件将显示文件夹结构,并且当您单击一个文件夹时,它将显示所选文件夹的文件。

模型视图样式还有更多内容吗?

是的,您会发现需要使用值转换器、不同的行为以及视觉状态管理器,才能拥有完成任何 Silverlight 项目所需的全部工具。要学习如何使用这些工具,只需访问 http://www.microsoft.com/design/toolbox/

该网站将为您提供掌握 Expression Blend 所需的免费培训。它还将涵盖设计原则,使您成为一名更好的设计师。真的,这再简单不过了。现在是加入“革命”的时候了。

历史

  • 2010 年 3 月 27 日:初始版本
© . All rights reserved.