Навигация

77

Навигация - крайне важная задача для приложений Silverlight, потому что мощные клиентские приложения чаще всего основаны на переключениях между разными окнами, в каждом из которых инкапсулирована отдельная задача. Чтобы создавать такие приложения в Silverlight, необходимо отказаться от приложений на одной странице и перейти к созданию приложений на многих страницах.

В приложениях Silverlight существуют два базовых способа замены страницы. Каждый из них соответствует определенным ситуациям. Первый, "самодельный", способ состоит в непосредственном манипулировании пользовательским интерфейсом вручную. Например, можно написать код, обращающийся к визуальному корневому элементу, удаляющий пользовательский элемент управления, представляющий первую страницу и добавляющий другой элемент, на котором отображена другая страница. Этот способ довольно простой, и для его реализации нужно написать сравнительно немного кода. Кроме того, он позволяет гибко управлять элементами страницы и создавать анимационные эффекты перехода.

Второй способ состоит в использовании встроенной навигационной системы Silverlight, основанной на двух элементах управления: Frame и Page. Базовая идея состоит в том, что один фрейм может выводить по очереди разные страницы. Фактически реализовать второй способ не легче, чем первый, однако он предоставляет ряд дополнительных средств, реализовать которые вручную было бы слишком трудоемкой задачей. Это такие средства, как информативные адреса URI, отслеживание страниц, глубокие ссылки, интеграция с историей браузера и т.д.

Базовая идея "самодельной" навигации состоит в программном изменении содержимого, отображаемого на странице Silverlight. Обычно изменение выполняется путем манипулирования контейнерами или элементами ContentControl. Для этого не нужно создавать огромное количество элементов управления и манипулировать ими в коде, намного легче задача решается с помощью разметки XAML. Необходим способ создания и загрузки отдельных элементов управления, каждый из которых размещается в отдельном файле XAML и представляет отдельную страницу.

Внедрение пользовательских элементов управления на страницу

Многие приложения Silverlight основаны на одной странице, выполняющей функции главного окна всего приложения. Вы можете изменить часть страницы путем загрузки нового содержимого, имитируя таким образом навигацию.

Один из примеров указанного способа — страница меню, переключающая различные страницы. В ней элемент управления Grid применяется для разбиения страницы на два раздела, отделенных друг от друга горизонтальным элементом GridSplitter. В верхнем разделе приводится список страниц, которые можно посетить. При выборе пункта списка соответствующее содержимое загружается в нижний раздел:

Окно, в котором пользовательские элементы управления загружаются динамически

Загрузить пользовательский элемент управления динамически несложно. Нужно лишь создать экземпляр соответствующего класса и добавить его в подходящий контейнер. Обычно в качестве контейнера используются элементы Border, ScrollViewer, StackPanel и Grid. На показанном рисунке используется элемент Border, наследующий класс ContentControl и рисующий рамку с помощью свойств BorderBrush и BorderThickness:

<UserControl x:Class="SilverlightTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="LayoutRoot" Background="White" Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="1.5*"></RowDefinition>
        </Grid.RowDefinitions>

        <ListBox SelectionChanged="lstPages_SelectionChanged">
            ...
        </ListBox>

        <Border Grid.Row="2" BorderBrush="SlateGray"  BorderThickness="1"
                Name="pagePlaceholder" Background="AliceBlue"></Border>
    </Grid>
</UserControl>

Если создать элемент Grid, не объявив ни строк, ни столбцов, в нем будет установлена единственная пропорциональная ячейка, занимающая все доступное пространство. Следовательно, добавление элемента управления в элемент Grid приведет к тому же результату, что и в элемент Border.

В разных примерах используются немного разные коды навигации, потому что необходимо учитывать типы добавляемых элементов управления. Для выяснения типа код проверяет объект ListBoxItem, на котором произошел щелчок мышью. При создании соответствующего пользовательского объекта используется рефлексия:

private void lstPages_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
            // Получение выбранного элемента
            string newPageName = ((ListBoxItem)e.AddedItems[0]).Content.ToString();

            // Создание экземпляра страницы
            Type type = this.GetType();
            System.Reflection.Assembly assembly = type.Assembly;
            UserControl newPage = (UserControl)assembly.CreateInstance(
                type.Namespace + "." + newPageName);

            // Вывод страницы на экран
            pagePlaceholder.Child = newPage;
}

За исключением кода рефлексии, для установки свойства Border.Child и вывода единственного пользовательского элемента управления используется аналогичная процедура. Для работоспособности примера в проект должны быть добавлены соответствующие элементы управления, например, TextBlockWrapping.xaml.

Сокрытие элементов

Создавать динамические страницы, как в предыдущем примере, можно не только добавляя и удаляя содержимое. Его можно временно скрыть. Это делается с помощью свойства Visibility, определенного в базовом классе UIElement и наследуемого всеми элементами:

panel.Visibility = Visibility.Collapsed;

В свойстве Visibility используется перечисление, содержащее два элемента: Visible и Collapsed (в WPF есть третий элемент — Hidden, который скрывает элемент, но на его месте выводит мерцающий маркер; в Silverlight элемент Hidden не поддерживается). Свойство Visibility можно установить для каждого элемента страницы отдельно, однако легче скрыть весь контейнер (например, объект Border, StackPanel или Grid).

Когда элемент скрыт, он не занимает места на странице и не получает фокус ввода. Остальное содержимое контейнера повторно размещается, занимая освободившееся пространство (конечно, если содержимое не позиционировано с фиксированными координатами на холсте Canvas).

Во многих приложениях используются панели, которые плавно сворачиваются или "убегают" в сторону. Для создания подобных эффектов необходимо применить средства анимации, встроенные в Silverlight. Процедура анимации плавно изменяет элемент, который нужно скрыть. Например, она сжимает или перемещает элемент. По завершении анимации свойству Visibility нужно присвоить значение, скрывающее элемент.

Управление корневым визуальным элементом

Описанный выше способ изменения страницы используется чаще других, однако он подходит не во всех ситуациях. Его главный недостаток состоит в том, что новое содержимое вставляется на существующую страницу. В предыдущем примере из-за этого в верхней части страницы постоянно выводится список. Это удобно, если список или панель инструментов должны быть всегда доступны, однако если нужно вывести совершенно новую страницу для решения другой задачи, это не очень удобно.

Альтернативный подход состоит в присвоении всей странице нового элемента управления вместо предыдущего. Для этого в качестве корневого визуального элемента управления используется простой контейнер. По мере необходимости можно загрузить пользовательские элементы в корневой элемент, а затем выгрузить. Однако учитывайте, что после запуска приложения сам корневой элемент не может быть изменен. Изменять можно только его содержимое.

Процедура запуска приложения Silverlight обычно создает экземпляр пользовательского элемента управления:

private void Application_Startup(object sender, StartupEventArgs e)
{
            this.RootVisual = new MainPage();
}

Для навигации применяется более гибкий подход — процедура создает простой контейнер (например, Border) или панель (например, Grid):

// Стартовый элемент управления
private Grid RootGrid = new Grid();

// Загрузка первой страницы 
private void Application_Startup(object sender, StartupEventArgs e)
{
     this.RootVisual = RootGrid;
     RootGrid.Children.Add(new MainPage());
}

Теперь можно перейти к другой странице, удалив первую решетку и добавив другую. Чтобы упростить решение, можете добавить статический метод в класс App:

public static void Navigate(UserControl newPage)
{
            // Извлечение объекта текущего приложения и его приведение к типу пользовательского 
            // элемента управления, производного от App
            App currentApp = (App)Application.Current;

            // Замена выводимой страницы
            currentApp.RootGrid.Children.Clear();
            currentApp.RootGrid.Children.Add(newPage);
}

Теперь можно использовать статический метод App.Navigate для навигации к любой странице.

История браузера

Описанный метод навигации обладает существенным ограничением: браузер ничего не знает о переходе с одной страницы на другую. Переход обратно выполняется с помощью элементов управления Silverlight, поэтому кнопка Back (Назад), расположенная на панели инструментов браузера, возвращает пользователя к предыдущей HTML-странице, а не к Silverlight (приложение Silverlight при этом завершается).

Можно создать систему навигации, более эффективно интегрированную в браузер и поддерживающую кнопку Back, но для этого нужно применить классы Frame и Page.

Пройди тесты
Лучший чат для C# программистов