16、校园探索者应用:设计与实现解析

校园探索者应用:设计与实现解析

1. 应用背景与需求

乔治梅森大学希望在其主网站上引入新功能,帮助潜在学生和访客探索校园。当前虽有校园地图可在线查看或下载打印,但缺乏与校园实际外观的关联,且学术部门等信息需单独查找并手动在地图上定位。因此,该应用的首个版本需具备以下功能:
1. 显示现有地图。
2. 展示单校区内所有建筑,允许用户选择并在地图上高亮显示。
3. 展示学校所有部门,用户选择部门时,显示部门信息并高亮显示部门主办公室所在建筑。
4. 当用户关注某建筑时,以缩略图形式展示相关图像或视频。
5. 用户选择图像或视频缩略图时,显示更详细视图(视频可播放、暂停、恢复、停止)。
6. 显示访客前往学校的主要道路列表,用户选择时,在地图上高亮显示路线,并可附带方向箭头、文字提示等。
7. 确保应用能在 Windows 和 OS X 系统及不同浏览器上运行。

Silverlight 是实现这些功能的理想选择,因其具备跨平台性,便于 .NET 程序员开发,且对图像和视频处理有良好支持。

2. 应用设计
2.1 用户界面设计
  • 界面元素可见性 :始终显示高细节地图的缩小版本,用户能看到地图上的建筑列表、学校部门和驾车路线。关注建筑时,能查看相关媒体。
  • XAML 文件实现 :使用多个 XAML 文件实现用户界面,包括主应用页面、地图页面和视频缩略图页面。特定页面的功能代码可保留在该页面,如顶部导航按钮和左侧导航选项,更适合作为页面代码而非可复用元素。
2.2 数据表示

在实现前需确定数据表示方式。经收集应用需存储和处理的信息,定义以下数据结构:
|数据类型|属性|
| ---- | ---- |
|学校|名称、缩写|
|校区|名称、地图|
|部门|名称、缩写、描述、建筑编号|
|校园地图|名称、全尺寸图像(路径、宽度、高度)、缩小尺寸图像(路径、宽度、高度)、零个或多个注释|
|地图注释|名称、描述、图像路径、类别|
|建筑|名称、关联地图信息、零个或多个图像和视频|
|建筑地图信息|建筑编号、网格单元、可在地图上高亮显示的位置|
|图像/视频|标题、媒体路径、宽度、高度|

数据模型将转化为对应 XML 文件结构的类,便于 XML 文件序列化、数据绑定,并通过 DisplayText 属性使 XAML 更简洁。部分 XML 文件示例如下:

<?xml version="1.0" encoding="utf-8"?>
<school name="George Mason University" initials="GMU">
<departments>
<department abbreviation="CS" name="Computer Science"
building="44" description="..."/>
</departments>
<campuses>
<campus name="Fairfax">
<mapdata name="Fairfax Campus" source="fairfax.png"
width="2400" height="2000"
downsource="fairfax_down.png"
downwidth="600" downheight="500">
<annotations>
<annotation name="From 66"
category="Driving Directions" description="..."
image="annotations/fairfax_directions_66.png"/>
</annotations>
</mapdata>
<buildings>
<building name="Enterprise Hall">
<mapinfo number="13" highlight="1470,1040,170,120" grid="E5"/>
<images>
<image caption="Stairs to center of campus"
source="/images/EnterpriseBasement.png"
width="100" height="100"/>
</images>
<videos>
<video caption="Outside Main Entrance Floor"
source="/videos/EnterpriseMainEntrance.wmv"
width="100" height="100"/>
<video caption="Outside Bottom Floor"
source="/videos/EnterpriseBottomFloor.wmv"
width="100" height="100"/>
</videos>
</building>
</buildings>
</campus>
</campuses>
</school>
2.3 应用打包

发送给客户端的文件分为三类:
- Silverlight 应用 :包含应用的 XAP 文件,用户浏览网站应用时下载,可在客户端浏览器缓存。
- 学校数据 :应用初始化后下载,原因如下:
- 更快显示用户界面(即使是进度条),提升用户体验。
- 可将学校数据保存到独立存储,下次加载更快,减少重复访问用户的服务器流量。使用独立存储需实现版本检查,查看服务器数据是否更新。
- 将学校特定信息置于 Silverlight 应用外,便于构建通用应用并出售给其他大学。
- 学校媒体文件 :校园各部分的图像和视频,作为网站一部分打包,文件路径存储在 XML 数据文件中,也可存储在 Silverlight Streaming 并引用。

3. 应用实现
3.1 辅助方法

应用有一个有用的扩展方法 GetHostAddress ,用于获取 Silverlight 应用所在服务器的路径,便于引用网站中的媒体文件:

public static class ApplicationExtensions
{
    public static string GetHostAddress(this Application app)
    {
        return (app.Host.Source.AbsoluteUri.Substring(0,
            app.Host.Source.AbsoluteUri.IndexOf(
                app.Host.Source.AbsolutePath)));
    }
}

使用示例:

video.Source = new Uri(App.Current.GetHostAddress() + "/test.wmv", 
    UriKind.Absolute);

该方法返回的字符串无尾随斜杠,若网站或应用在虚拟目录下,需修改此方法。

3.2 XAML 组织

应用由四个 XAML 文件组成(不包括 App.xaml):
- MainPage.xaml :包含应用主要部分,除交互式地图及其关联信息面板外的所有内容。使用控制模板更改 ListBox 和导航按钮外观,通过暴露 XML 命名空间将 Map.xaml 控件放置在页面上。还包含一个数据下载时覆盖整个界面的弹出窗口,通过 Opacity 实现磨砂效果。

<UserControl x:Class="chapter15.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ce="clr-namespace:chapter15">
    <!-- ... -->
    <ce:Map Grid.Row="1" Grid.Column="1" x:Name="mapControl"/>
    <!-- ... -->
</UserControl>
<Popup x:Name="startupPopup">
    <Canvas Background="White" Opacity="0.7"
        Width="860" Height="815">
        <TextBlock x:Name="dataDownloadProgressText" Text=""
            Canvas.Left="400" Canvas.Top="400"/>
    </Canvas>
</Popup>
  • Map.xaml :包含四个主要元素:顶部信息面板(包含文本、图像、视频)、用户界面上的缩小地图、放大地图和查看大图/视频的弹出窗口。
  • VideoThumbnail.xaml :用于在 Map.xaml 中显示带播放按钮覆盖的视频。
  • ErrorFrame.xaml :捕获并显示未处理异常,提供比浏览器错误更流畅的用户体验。
3.3 地图交互列表框

应用左侧列表框展示地图上的建筑、学校部门或驾车路线等注释。默认 ListBox 外观不适合该应用,需修改。通过定义新的控制模板,去除 ListBox 边框,修改 ListBoxItem 控制模板以去除部分动画,使项目外观更扁平。

<Style x:Key="ListBoxStyle1" TargetType="ListBox">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBox">
                <Grid>
                    <ScrollViewer x:Name="ScrollViewer"
                        BorderThickness="0"
                        Padding="{TemplateBinding Padding}">
                        <ItemsPresenter/>
                    </ScrollViewer>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<vsm:VisualState x:Name="MouseOver">
    <Storyboard>
        <DoubleAnimationUsingKeyFrames
            Storyboard.TargetName="HoverOverlay"
            Storyboard.TargetProperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.75"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</vsm:VisualState>
<ListBox x:Name="mapItemsListBox"
    ItemsSource="{Binding Mode=OneWay}"
    Width="190" Height="500"
    Canvas.Left="15" Canvas.Top="75"
    Style="{StaticResource ListBoxStyle1}"
    SelectionChanged="mapItemsListBox_SelectionChanged"
    ItemContainerStyle="{StaticResource ListBoxItemStyle1}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=DisplayText}" Height="25"
                    Foreground="Black" FontSize="10"
                    VerticalAlignment="Top"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

任何具有 DisplayText 属性的对象都可用于此 ListBox 的数据绑定,如 Department 类:

public class Department
{
    [XmlAttribute("name")]
    public string name { get; set; }
    public string DisplayText
    {
        get
        {
            return (this.name);
        }
    }
    // ...
}
3.4 地图缩放

应用核心是交互式地图,其有趣功能是地图缩放。通过两张图像实现缩放,高分辨率地图(2400×2000 PNG 文件)先在图形程序中缩放到 600×500,再用于应用。用户按住鼠标左键时,从 2400×2000 图像中截取 200×200 矩形作为放大切片显示,并附带信息文本。

<Image x:Name="zoomedMapImage"
    Width="2400" Height="2000" Visibility="Collapsed">
    <Image.Clip>
        <RectangleGeometry Rect="0,0,200,200">
            <RectangleGeometry.Transform>
                <TranslateTransform X="0" Y="0"
                    x:Name="zoomedMapClipTransform"/>
            </RectangleGeometry.Transform>
        </RectangleGeometry>
    </Image.Clip>
    <Image.RenderTransform>
        <TranslateTransform X="0" Y="0"
            x:Name="zoomedMapTransform" />
    </Image.RenderTransform>
</Image>

鼠标点击事件处理:

private void mapCanvas_MouseLeftButtonDown(object sender,
    MouseButtonEventArgs e)
{
    zoomedMapImage.Visibility = Visibility.Visible;
    zoomBorder.Visibility = Visibility.Visible;
    gridInfoBorder.Visibility = Visibility.Visible;
    setZoomedPosition(e.GetPosition(mapCanvas));
}

setZoomedPosition 方法计算切片位置和显示内容:

private void setZoomedPosition(Point p)
{
    zoomedMapImage.SetValue(Canvas.LeftProperty, p.X - 100);
    zoomedMapImage.SetValue(Canvas.TopProperty, p.Y - 100);
    zoomedMapClipTransform.X = ((p.X) / 600) * 2400 - 100;
    zoomedMapClipTransform.Y = ((p.Y) / 500) * 2000 - 100;
    zoomedMapTransform.X = -1 * zoomedMapClipTransform.X;
    zoomedMapTransform.Y = -1 * zoomedMapClipTransform.Y;
    // 其他代码
}

用户按住鼠标左键拖动放大切片时,通过处理 MouseMove 事件实现:

private void mapCanvas_MouseMove(object sender, MouseEventArgs e)
{
    setZoomedPosition(e.GetPosition(mapCanvas));
}
graph TD;
    A[用户按下鼠标左键] --> B[显示放大切片和信息文本];
    B --> C[调用 setZoomedPosition 方法];
    C --> D[设置切片位置和裁剪区域];
    D --> E[移动切片到正确位置];
    F[用户拖动鼠标] --> G[再次调用 setZoomedPosition 方法];
    G --> D;

校园探索者应用:设计与实现解析

4. 地图其他功能实现
4.1 建筑高亮

数据中包含每栋建筑的位置信息,以便在地图上高亮显示。通过在 XAML 中创建一个初始不可见的黑色椭圆来实现高亮效果:

<Ellipse Stroke="Black" StrokeThickness="5" x:Name="highlight"
    Visibility="Collapsed"/>

在代码中,根据建筑的地图信息设置椭圆的可见性和位置:

private void highlightBuilding(Building building)
{
    if (!string.IsNullOrEmpty(building.mapinfo.highlight))
    {
        string[] pieces = building.mapinfo.highlight.Split(',');
        highlight.Visibility = Visibility.Visible;
        highlight.SetValue(Canvas.LeftProperty, Convert.ToDouble(pieces[0]));
        highlight.SetValue(Canvas.TopProperty, Convert.ToDouble(pieces[1]));
        highlight.Width = Convert.ToDouble(pieces[2]);
        highlight.Height = Convert.ToDouble(pieces[3]);
    }
}
4.2 地图注释

地图注释用于为地图添加信息,本应用主要用于显示驾车路线。创建注释有两种方法:
- 使用 Silverlight 绘图原语 :如使用 TextBlock 显示文本,使用线条和椭圆等形状在地图上绘制信息。这种方法可能需要使用 Expression Blend 创建 XAML 或自定义工具。
- 在图形编辑器中绘制 :本应用采用此方法,在图形编辑器(如 Paint.NET)中打开原始地图图像,在新图层上绘制注释,然后删除原始地图图层,保留注释在透明图像上。这样注释可以轻松显示在 600×500 地图和 2400×2000 图像的放大切片上。

以下是显示注释的代码:

public void showAnnotation(string annotationImageSource)
{
    clear();
    BitmapImage imageSource = new BitmapImage();
    imageSource.SetSource(SchoolData.GetMapAnnotation(annotationImageSource));
    annotationMapMini.Source = imageSource;
    annotationMapMini.Visibility = Visibility.Visible;
}

在 XAML 中,注释图像放置在主地图图像之后,确保其显示在主地图之上:

<Image x:Name="mainMap" Width="600" Height="500"
    Canvas.Left="0" Canvas.Top="0"/>
<Image x:Name="annotationMapMini" Width="600" Height="500"
    Canvas.Left="0" Canvas.Top="0" Visibility="Collapsed"/>

开发过程中遇到 PNG 图像颜色空间为索引格式,与 Silverlight 透明度不兼容的问题,可使用 ImageMagick 将 PNG 从索引颜色空间转换为 RGBA:

convert.exe <source image> -channel RGBA <destination image>
4.3 信息面板

信息面板位于地图上方,包含一行始终可见的文本,下方可选择性显示额外文本、图像或视频。右侧有一个箭头,用户点击可展开或折叠面板。

箭头的实现如下:

<StackPanel Orientation="Horizontal" Canvas.Left="518" Canvas.Top="0" >
    <TextBlock Text="Click to collapse" x:Name="arrowLabel"
        Foreground="White" FontSize="12" Margin="0 0 5 0"/>
    <Image Source="arrow_down.png" x:Name="arrowButton" Width="18" Height="18"
        MouseLeftButtonUp="arrow_MouseLeftButtonUp">
        <Image.RenderTransform>
            <RotateTransform Angle="0" CenterX="9" CenterY="9" 
                x:Name="arrowRotation"/>
        </Image.RenderTransform>
    </Image>
</StackPanel>

点击箭头的事件处理代码:

private void arrow_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    if (upperPanelExpanded)
    {
        scrollUp.Begin();
        arrowRotation.Angle = 90;
        infoText.Visibility = Visibility.Collapsed;
        mediaScrollViewer.Visibility = Visibility.Collapsed;
        arrowLabel.Text = "Click to expand";
        upperPanelExpanded = false;
    }
    else
    {
        scrollDown.Begin();
        arrowRotation.Angle = 0;
        infoText.Visibility = informationalTextVisibility;
        mediaScrollViewer.Visibility = mediaListVisibility;
        arrowLabel.Text = "Click to collapse";
        upperPanelExpanded = true;
    }
}

图像和视频的容器是一个位于 ScrollViewer 内的空 StackPanel ,方便在内容过多时提供滚动功能:

<ScrollViewer HorizontalScrollBarVisibility="Auto"
    VerticalScrollBarVisibility="Disabled"
    Height="130" Canvas.Left="15" Canvas.Top="30" Width="600"
    Visibility="Collapsed" x:Name="mediaScrollViewer">
    <StackPanel Orientation="Horizontal" x:Name="mediaStackPanel"/>
</ScrollViewer>

当用户选择建筑或部门时,将相关视频和图像添加到 mediaStackPanel

mediaStackPanel.Children.Clear();
if (SchoolData.school.campuses[0].buildings[i].videos != null &&
    SchoolData.school.campuses[0].buildings[i].videos.Count > 0)
{
    for (int j = 0; 
        j < SchoolData.school.campuses[0].buildings[i].videos.Count; 
        j++)
    {
        VideoThumbnail video = new VideoThumbnail();
        video.Source = new Uri(App.Current.GetHostAddress() +
            SchoolData.school.campuses[0].buildings[i].videos[j].source,
            UriKind.Absolute);
        video.MouseLeftButtonUp += media_MouseLeftButtonUp;
        video.Margin = new Thickness(10, 0, 10, 0);
        video.Tag = j;
        mediaStackPanel.Children.Add(video);
    }
}

点击图像或视频时,触发 media_MouseLeftButtonUp 事件,显示图像或视频并打开弹出窗口:

<Popup Canvas.Left="20" Canvas.Top="40"
    Width="300" Height="300" x:Name="imagePopup">
    <Border BorderBrush="Black" BorderThickness="1"
        Background="Black">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="24"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <TextBlock x:Name="zoomedMediaHeader"
                HorizontalAlignment="Center"
                Text="Front Entrance" Foreground="Red"/>
            <Image x:Name="zoomedImage" Width="300" Height="300"
                Grid.Row="1" Visibility="Collapsed"/>
            <MediaElement x:Name="zoomedVideo" AutoPlay="False"
                Width="300" Height="300" Grid.Row="1"
                Visibility="Visible"/>
            <StackPanel HorizontalAlignment="Center" Grid.Row="2"
                Orientation="Horizontal" Height="24">
                <Button x:Name="videoPlayStopButton" Content="PLAY"
                    Margin="2" Click="videoPlayStopButton_Click"/>
                <Button x:Name="videoPauseResumeButton" Content="PAUSE"
                    Margin="2" Click="videoPauseResumeButton_Click"/>
                <Button x:Name="popupButton" Content="CLOSE"
                    Click="popupButton_Click" Margin="2"/>
            </StackPanel>
        </Grid>
    </Border>
</Popup>

信息面板的展开和折叠通过资源字典中的两个故事板实现:

<UserControl.Resources>
    <Storyboard x:Name="scrollUp" Storyboard.TargetName="upperPanel"
        Storyboard.TargetProperty="Height">
        <DoubleAnimationUsingKeyFrames>
            <LinearDoubleKeyFrame KeyTime="0:0:0" Value="170"/>
            <LinearDoubleKeyFrame KeyTime="0:0:0.2" Value="30"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
    <Storyboard x:Name="scrollDown" Storyboard.TargetName="upperPanel"
        Storyboard.TargetProperty="Height">
        <DoubleAnimationUsingKeyFrames>
            <LinearDoubleKeyFrame KeyTime="0:0:0" Value="30"/>
            <LinearDoubleKeyFrame KeyTime="0:0:0.2" Value="170"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</UserControl.Resources>
5. 总结

该 Silverlight 应用为用户提供了一个探索校园的交互式平台,实现了显示地图、高亮建筑、展示部门信息、播放媒体等功能。通过合理的设计和实现,确保了应用在不同平台和浏览器上的兼容性。不过,该应用仍有改进空间,例如可以将交互式地图与课程和学生日程表关联起来,在地图上提供更多信息等。Silverlight 为开发者提供了丰富的功能和表现力,开发者可以充分利用这一平台创建更强大的应用。

graph LR;
    A[用户选择建筑/部门] --> B[显示建筑信息和媒体缩略图];
    B --> C[点击媒体缩略图];
    C --> D[打开弹出窗口显示详细媒体];
    E[点击箭头] --> F[展开/折叠信息面板];
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值