UWP: Новые контролы (часть 2). SplitView

Сегодня мы рассмотрим очередной новый контрол, который позволяет создавать адаптивный интерфейс — и это будет SplitView. Обычно этот элемент управления используется для создания меню, но на самом деле он так же позволяет объявить две панели, с совершенно любым содержанием. Основной синтаксис SplitView выглядит следующим образом:

<SplitView IsPaneOpen="False" 
           DisplayMode="CompactInline"  
           PaneBackground="Beige" 
           OpenPaneLength="200" 
           CompactPaneLength="30">
    <SplitView.Pane>
        
    </SplitView.Pane>
    <SplitView.Content>

    </SplitView.Content>
</SplitView>

Свойство DisplayMode можно установить в одно из следующих значений:

  • CompactInline — панель Pane поддерживает компактный режим. Когда она будет расширяться — все содержимое будет смещено, чтобы обеспечить достаточное пространство для расширенной панели;
  • CompactOverlay — данный режим такой же как и предыдущий, но когда панель расширяется она не влияет на остальное содержание. Панель будет размещена над остальным контентом;
  • Inline — поддерживается только в расширенном режиме. Если меню отображается, то весь другой контент будет смещен, чтобы предоставить достаточно места для панели;
  • Overlay — поддерживается только в расширенном режиме. Не влияет на весь другой контент, потому что панель будет размещена над содержанием;

С помощью свойства IsPaneOpen мы можем определить — панель находится в компактном режиме (закрыта) или в расширенном (открыта). Таким образом, если вы установите в свойство IsPaneOpen значение false, то панель будет отображаться в компактном режиме, а если в true — то панель будет отображаться в расширенном режиме.

Таким образом вы можете увидеть, что у SplitView нет ничего, связанного с меню, но у для вас не составит труда разместить что-то вроде ListView внутри и объявить элементы меню. Разрабатывайте и располагайте пункты меню так, что бы пользователи видели только значки в компактном режиме и все остальное в расширенном, а также работайте с VisualStateManager в котором можно изменять DisplayMode и IsPaneOpen свойства в режиме реального времени.

Советую придерживаться этих рекомендаций для создания своего собственного меню:

  • Реализовывать три состояния для меню: расширенное, компактное и ультракомпактное для различных размеров экрана. Если у вас достаточно места, вы можете показывать панель в расширенном режиме без каких-либо проблем, но если у вас мало места — вы можете показывать только иконки. И если у вас совсем нет места (телефон в портретной ориентации), вы должны активировать ультракомпактный режим и скрыть Pane полностью;
  • В компактном и ультракомпактный состоянии добавить кнопку “гамбургера” над меню, которое будет открывать панель в Overlay режиме. Таким образом, вы сможете открыть меню даже в ультракомпактном режиме;
    clip_image002
  • Использование ListView для меню;

Таким образом, ваш VisualStateManager может выглядеть следующим образом (CompactInline по-умолчанию, и IsPaneOpen установлен в True):

<VisualState x:Name="Expanded">
    <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="900"></AdaptiveTrigger>
    </VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="Compact">
    <VisualState.Setters>
        <Setter Value="False" Target="splitView.IsPaneOpen"></Setter>
        <Setter Value="CompactOverlay" 
           Target="splitView.DisplayMode"></Setter>
    </VisualState.Setters>
    <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="500"></AdaptiveTrigger>
    </VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="UltraCompact">
    <VisualState.Setters>
        <Setter Value="False" Target="splitView.IsPaneOpen"></Setter>
        <Setter Value="Overlay" Target="splitView.DisplayMode"></Setter>
    </VisualState.Setters>
    <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="0"></AdaptiveTrigger>
    </VisualState.StateTriggers>
</VisualState>

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

Ссылка на источник: UWP: New Controls (part 2 – SplitView)

Реклама
Tagged with: , , , ,
Опубликовано в Development, Windows 10

UWP: Новые контролы (часть 1). RelativePanel

Сегодня мы начнем с Вами обсуждать новые контролы для операционной системы Windows 10. Начнем мы с RelativePanel.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <TextBlock Grid.ColumnSpan="2" 
               Text="This is a header" 
               Style="{ThemeResource HeaderTextBlockStyle}" />
    <Image Grid.Row="1" 
           MaxWidth="800"  
           Source="Assets\drone.jpg" 
           VerticalAlignment="Top" />
    <TextBlock Grid.Column="1" 
               Grid.Row="1" 
               Text="This is a text about drones."
               Margin="10,0,10,0" 
               TextWrapping="Wrap" />
</Grid>

В данном участке кода своеобразная сетка, в ячейках которой расположены 2 текстовых поля и картинка. Если мы запустим данный код — получим следующее:

Вроде как все выглядит довольно-таки прилично, но есть проблема, Windows 10 позволяет изменять размер окон, а так же дополнительно можно запустить это приложение на телефоне. По этому невооруженным глазом заметно, что у меня недостаточно места, чтобы показать изображение и текст на меньшем размере окна. Поэтому мне хотелось бы создать универсальный интерфейс, который я смог бы изменять динамически при нехватке места. Впринципе, нам попросту нужно поместить текст под изображением и VisualStateManager это лучший способ для этого. Но я использую Grid, у которого два столбца и две строки, а для меньших дисплеев мне нужна одна колонка и три рядка, которыми практически невозможно управлять в VisualStateManager даже для моего примера (реальные интерфейсы являются гораздо более сложным). Вот почему разработчики дублируют некоторые части интерфейса и просто работать с Visibility внутри VisualStateManager при  создании универсальных приложений для Windows 8.1.

Начиная с Windows 10 у разработчиков есть возможность использовать новый контейнер, который позволяет избежать проблем с сеткой — это RelativePanel. Давайте посмотрим на тот же пример, но с использованием RelativePanel.

<RelativePanel >
    <TextBlock Name="header" 
               Text="This is a header" 
               Style="{ThemeResource HeaderTextBlockStyle}" />
    <Image Name="image"
           MaxWidth="800"
           RelativePanel.Below="header"  
           Source="Assets\drone.jpg" />
    <TextBlock Name="text"
               Text="This is a text about drones." 
               Margin="10,0,10,0" 
               TextWrapping="Wrap" 
               RelativePanel.AlignTopWith="image" 
               RelativePanel.RightOf="image" />
</RelativePanel>

Как вы видите в случае RalativePanel можно объявить позицию встроенных элементов, использующих свойства зависимостей. Их очень легко переформатировать, используя VisualStateManager. Например, вы можете использовать следующий код, чтобы показать тот же интерфейс в один ряд:

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="Normal">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="900" />
            </VisualState.StateTriggers>
        </VisualState>
        <VisualState x:Name="Mobile">
            <VisualState.Setters>
                <Setter Value="" Target="text.(RelativePanel.AlignTopWith)" />
                <Setter Value="image" Target="text.(RelativePanel.Below)" />
                <Setter Value="" Target="text.(RelativePanel.RightOf)" />
                <Setter Value="0,10,0,10" Target="text.Margin" />
            </VisualState.Setters>
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="0" />
            </VisualState.StateTriggers>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

После того, как окно меньше, чем 900 пикселей, текст будет перемещен ниже изображения. Обратите особое внимание, как объявлять свойства зависимостей в атрибуте Target.

Источник: UWP: New Controls (part 1 – RelativePanel)

Tagged with: , , , ,
Опубликовано в Development, Windows 10

UWP: Новые возможности Visual State Manager (часть 2)

В предыдущем посте я рассказал о двух новых возможностях Visual State Manager’a: Setter’ах и адаптивных триггерах. Как вы увидели — адаптивные триггеры ограничивается только размером экрана, т.е. для других необходимых целей применить их не получится. Но в реальных приложениях вам может потребоваться  менять свой интерфейс на основе множества различных параметров, таких как Xbox контроллер, ориентация экрана, наличия данных и т.д. Таким образом, в этой статье я хочу показать, как продлить существующую инфраструктуру адаптивных триггеров, создав свой собственный.

Трудно найти приложение, которое не подключен к Интернету. Но получение данных из занимает некоторое время. Обычно я использую ProgressRing, что бы показать, что получение данных продолжается и я использую VisualStateManager, чтобы показать или скрыть элементы, основанные на имеющихся данных. Обычно есть три состояния: загрузка, загружено и ошибка. Естественно будет необходимо реализовать некоторый код, который изменит состояние приложения на основе состояния «модели представления». Давайте посмотрим, может ли мы вообще реализовывать свои собственные триггеры и поможет ли нам это в избежании создания лишнего кода.
Прежде всего, мы должны проверить, готов ли наш класс «вьюмодели» к использованию вместе с триггерами. Лучше всего реализовать свойство, которое показывает текущее состояние, а также событие, которое срабатывает каждый раз при изменении данного свойства. Конечно, лучше всего это реализовать в базовом классе.

public enum StateEnum
{
    Loading,
    Loaded,
    Error
}

public class StateChangeEventArgs:EventArgs
{
    public StateEnum State { get; set; }
}

public delegate void StateChangedDelegate(object model, StateChangeEventArgs args);

public class PageViewModel
{
    public event StateChangedDelegate StateChanged;

    public void InitModel()
    {
        if (StateChanged != null) StateChanged.Invoke(this, 
            new StateChangeEventArgs() { State = StateEnum.Loading });
        //load data
        if (StateChanged != null)
            StateChanged.Invoke(this,
                new StateChangeEventArgs() { State = StateEnum.Loaded });
    }
}

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

<Page.Resources>
    <st:PageViewModel x:Name="model"></st:PageViewModel>
</Page.Resources>

Самое время для создания собственного триггера. Для этого вы должны создать новый класс, который будет наследовать класс StateTriggerBase. Внутри класса можно объявить любые методы и свойства, но вы должны помнить, что вам будет необходимо вызывать метод SetActive. Благодаря ему можно активировать или деактивировать свой триггер. Например, я реализовал следующий класс:

public class DataTrigger: StateTriggerBase
{
    private PageViewModel model;

    public PageViewModel Model
    {
        get
        {
            return model;
        }
        set
        {
            model = value;
            model.StateChanged += Model_StateChanged;
        }
    }

    public string StateOfModel { get; set; }

    private void Model_StateChanged(object model, StateChangeEventArgs args)
    {
        SetActive(args.State.ToString().Equals(StateOfModel));
    }
}

Как вы можете видеть у меня есть два свойства, которые позволяют установить ссылку на текущую вьюмодель и определить состояние, которое мы будем использовать для активации триггера. После того, как вьюмодель инициализируется — мы активируем или деактивируем триггер с помощью обработчика события StateChanged.

В итоге у меня получились следующие состояния в XAML:

<VisualState x:Name="Loading">
    <VisualState.Setters>
        <Setter Target="gridView.Visibility" Value="Collapsed"></Setter>
        <Setter Target="progress.Visibility" Value="Visible"></Setter>
    </VisualState.Setters>
    <VisualState.StateTriggers>
        <st:DataTrigger Model="{StaticResource model}" 
           StateOfModel="Loading"></st:DataTrigger>
    </VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="Loaded">
    <VisualState.Setters>
        <Setter Target="gridView.Visibility" Value="Visible"></Setter>
        <Setter Target="progress.Visibility" Value="Collapsed"></Setter>
    </VisualState.Setters>
    <VisualState.StateTriggers>
        <st:DataTrigger Model="{StaticResource model}" 
           StateOfModel="Loaded"></st:DataTrigger>
    </VisualState.StateTriggers>
</VisualState>

Ссылка на источник: UWP: New features of Visual State Manager (part 2)

Tagged with: , , , , ,
Опубликовано в Development, Windows 10

UWP: Новые возможности Visual State Manager (часть 1)

Если вы собираетесь разрабатывать UWP приложения для Windows 10, то вы должны думать об адаптивном интерфейсе, который будет хорошо выглядеть на всех устройствах Windows, с различными размерами экрана, ориентацией и разрешениями. Visual State Manager обрел новый и очень хороший подход к реализации адаптивного интерфейса. Он позволяет объявить различные состояния интерфейса с возможностью изменения текущего состояния в режиме исполнения. Вот почему Microsoft продолжает вкладывать средства в этот компонент и сегодня разработчики могут использовать еще две новых особенности.

Setters

Если вы собираетесь использовать анимацию, чтобы изменить состояние — можно продолжать использовать старый подход с раскадровкой, но во многих случаях анимация не нужна. Например, если вы хотите изменить свой макет, потому что пользователь изменил ориентацию экрана, вы должны очень быстро изменять свойства элементов управления. Так, разработчики обычно используют ObjectAnimationUsingKeyFrame применяя все необходимые изменения за 0 секунд:

<Storyboard>
    <ObjectAnimationUsingKeyFrames 
        Storyboard.TargetName="itemListView" 
        Storyboard.TargetProperty="Visibility">
        <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
    </ObjectAnimationUsingKeyFrames>
</Storyboard>

Вы можете видеть, что этот подход не является оптимальным, поскольку он требует использования нескольких сложных объектов со многими параметрами, чтобы сделать простую вещь. Вот почему новый элемент XAML — Setter  может быть очень полезным. Например, вы можете переписать то же самое с его помощью:

<VisualState.Setters>
    <Setter Target="comboBox.Visibility" Value="Collapsed"></Setter>
</VisualState.Setters>

Это гораздо понятнее и чище. Разработчики должны объявить имя свойства и новое значение в выбранном состоянии. Если вы хотите, вы можете смешивать Setters и Storyboard в том же состоянии.

Адаптивные триггеры

Конечно, этого не достаточно, чтобы объявить все возможные состояния — разработчики должны реализовать код, который позволяет изменить состояние динамически. Например, если вы собираетесь изменить состояние в зависимости от размера экрана, необходимо реализовать обработчик событий для события SizeChanged и использовать метод GoToState класса VisualStateManager. Но в этом случае можно легко сделать ошибку или запутаться, не говоря уже об возможном дублировании кода в разметке. Вот почему Microsoft реализовала инфраструктуру для триггеров состояния. Он позволяет объявить один триггер или набор триггеров внутри XAML, чтобы понять, какое состояние должно применяться. Таким образом, вы можете объявить все необходимые правила без кода.

В текущей версии Microsoft представила только один триггер — AdaptiveTrigger. Кроме того, вы также можете разработать свои собственные триггеры.

В следующем коде вы можете увидеть использование AdaptiveTrigger:

<VisualState x:Name="Normal">
    <VisualState.Setters>
        <Setter Target="comboBox.Visibility" Value="Visible"></Setter>
    </VisualState.Setters>
    <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="700"></AdaptiveTrigger>
    </VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="Mobile">
    <VisualState.Setters>
        <Setter Target="comboBox.Visibility" Value="Collapsed"></Setter>
    </VisualState.Setters>
    <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="0"></AdaptiveTrigger>
    </VisualState.StateTriggers>
</VisualState>

Как вы видите у AdaptiveTrigger есть только два параметра: MinWindowWidth и MinWindowHeight. Эти параметры позволяют переключать состояние окна в зависимости от размера. В нашем примере, если у нас ширину окна меньше, чем 700 пикселей, мы прячем элемент с именем comboBox.

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

Ссылка на источник: UWP: New features of Visual State Manager (part 1)

Tagged with: , , , , , , ,
Опубликовано в Development, Windows 10

Windows 10, UWP и DeviceFamily — специфичные ресурсы

Как вы уже знаете, благодаря Windows 10  вы можете:

  • писать конкретные приложения для каждого семейства устройств (мобильных, настольных, Xbox, и т.д.).
  • писать приложения, которые будут работать на всех из них.

в итоге можно быть уверенным ,что в конце концов придет момент, когда надо будет адаптировать:

  • UX для работы с различными устройствами, имеющими различные механизмы ввода/вывода — например, клавиатуры, мыши, ручки, сенсор, игровые контроллеры, взгляд, жест, речь и т.д., а также для больших/ маленьких/нескольких экранов или их отсутствия и т.п..
  • функционал, который работает с различными сборками Windows, построен на общем ядре используя различные «контракты» для работы со специфичным API.

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

  • новый контрол RelativePanel
  • новые возможности Visual State Management, которые помогают автоматически реагировать на изменение размеров (AdaptiveTrigger) и моментальное изменение свойств (даже относительное позиционирование в RelativePanel)

Эта возможность поможет нам лучше реагировать на «размер экрана» и, возможно, это лучше, чем привязывать поведение непосредственно к семейству устройств (настольные мобильные и т.п.), т.к. становится все труднее узнать, какое железо может лежать в том или ином устройстве.

Все это кажется достаточно очевидным, если говорить о настольных компьютерах — мы привыкли к тому, что у настольного компьютера может быть 2 или 3 монитора с различными разрешениями и разной плотностью пикселей и т.п. В таком случае набор железа очень гибок и не является фиксированным.

Но если брать во внимание семейство «мобильных» — то все было логично, т.к. это всегда означало, что у устройства маленький экран, он один и имеет  сенсорный ввод. Но все эти утверждения были верны лишь до выхода Windows 10 на рынок с ее возможностью Continuum, где, внезапно, такое устройство как телефоне может получить большой монитор и мышь.

Для того, что бы разобраться, как же все-таки разбивать ресурсы, если нам это понадобилось, было собрано небольшое демо и вот как оно выглядит на разных платформах:

Capture

это просто чистый проект, в котором DataContext задается из кода страницы:

using System;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace App4
{
   public sealed partial class MainPage : Page
   {
      public MainPage()
      {
        this.InitializeComponent();
        this.Loaded += OnLoaded;
      }

      private void OnLoaded(object sender, RoutedEventArgs e)
      {
         Random r = new Random();

         this.DataContext =
         Enumerable.Range(1, 100).Select(
         i =>
         new SolidColorBrush(
           Windows.UI.Color.FromArgb(
             0xFF,
             (byte)r.Next(0, 255),
             (byte)r.Next(0, 255),
             (byte)r.Next(0, 255))));
      }
   }
}

Как вы видите это попросту список цветов, который цепляется к GridView для отображения на страничке:

<Page
      x:Class="App4.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App4"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

      <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
         <GridView ItemsSource="{Binding}" ItemTemplate="{StaticResource myTemplate}">
         </GridView>
      </Grid>
</Page>

а также подключенные ресурсы в файле App.xaml:

<Application
      x:Class="App4.App"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App4"
      RequestedTheme="Light">

    <Application.Resources>
      <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
          <ResourceDictionary Source="Resources.xaml"/>
        </ResourceDictionary.MergedDictionaries>
      </ResourceDictionary>
    </Application.Resources>
</Application>

Ну и конечно же 2 папки с 2-мя файлами ресурсов, которые выбираются автоматически в зависимости от устройства.
квадратный шаблон:

<ResourceDictionary
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App4.DeviceFamily_Desktop">

    <DataTemplate x:Key="myTemplate">
      <Rectangle Width="100" Height="100" Fill="{Binding}"/>
    </DataTemplate>
</ResourceDictionary>

и круглый шаблон:

<ResourceDictionary
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App4.DeviceFamily_Mobile">

    <DataTemplate x:Key="myTemplate">
       <Ellipse Width="100" Height="100" Fill="{Binding}"/>
    </DataTemplate>
</ResourceDictionary>

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

<Page
      x:Class="App4.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App4"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Page.Resources>
      <DataTemplate x:Key="square">
        <Ellipse Width="100" Height="100" Fill="{Binding}"/>
      </DataTemplate>
      <DataTemplate x:Key="round">
        <Rectangle Width="100" Height="100" Fill="{Binding}"/>
      </DataTemplate>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="stateGroups">
          <VisualState x:Name="Default"/>
           <VisualState x:Name="Mobile">
            <VisualState.StateTriggers>
             <local:DeviceFamilyTrigger DeviceFamily="Windows.Mobile"/>
            </VisualState.StateTriggers>
            <VisualState.Setters>
              <Setter Target="gridview.ItemTemplate" Value="{StaticResource round}"/>
            </VisualState.Setters>
          </VisualState>
        </VisualStateGroup>
      </VisualStateManager.VisualStateGroups>
      <GridView x:Name="gridview" ItemsSource="{Binding}"
                ItemTemplate="{StaticResource square}">
      </GridView>
    </Grid>
</Page>
namespace App4
{
    using Windows.System.Profile;
    using Windows.UI.Xaml;

    class DeviceFamilyTrigger : StateTriggerBase
    {
      public DeviceFamilyTrigger()
      {
        base.SetActive(AnalyticsInfo.VersionInfo.DeviceFamily == this.DeviceFamily);
      }
      public string DeviceFamily { get; set; }
    }
}

Как вы видите, в Windows 10 у нас появилась дополнительная работа — все состояния (для различных размеров настольного приложения + мобильное отображение и т.п) необходимо\можно реализовывать в одном файле в одном приложении, но Microsoft предоставил нам достаточное количество способов решения данной задачи: разбитие файлов ресурсов или страничек по папкам (папка будет выбираться в зависимости от устройства, на котором запустилось приложение), реализовать различный интерфейс в зависимости от размеров экрана, использовать триггеры в визуальных состояниях.

Ссылка на источник: Windows 10, UWP and DeviceFamily-Specific Resource Dictionaries

Tagged with: , , , , , , ,
Опубликовано в Development, Uncategorized, Windows 10

Windows 10: как использовать расширение IoT для Raspberry Pi 2 (часть 2)

В предыдущем посте мы обсуждали возможность добавления расширения для универсальных проектов и сделали краткий обзор по классам GPIO. Сегодня я собираюсь продолжить обзор расширения IoT и обсудить I2C и его классы.

МЫ будем работать с датчиком MPU6050, который предоставляет данные из гироскопа и акселерометра. Эти данные очень полезны для людей, которые хотели бы создать свои собственные беспилотные летательные аппараты (дроны) и вертолеты.

Этот датчик не требует какой-либо пайки и содержит преобразователь напряжения (с 5V до 3,3V) (чип 6050 использует питание 3,3V),  что позволяет использовать питание от ESC или других плат с выводом в 5V.

Для общения MPU 6050 использует I2C концентратор. Таким образом, для того, чтобы подготовить датчик к работе, необходимо подключить 5V и GND контакты к VCC на Raspberry и GND на датчике. Кроме того, вы должны подключить SDA и SCL контакты. У этого датчика нет светодиодов,  поэтому непросто понять все ли в порядке. Так что самый простой способ проверить работает он или нет — начать разработку.

Все необходимые классы вы можете найти в пространстве имен Windows.Devices.I2c и первый из них — это I2cDevice. Каждое устройство, к которому вы подключаетесь используя концентратор I2C, должно быть связано с объектом класса I2cDevice и только благодаря этому объекту разработчики могут общаться с устройством. Наиболее распространенные методы это Read и Write, которые работают с массивом байтов для получения или отправления данных. Но в большинстве случаев вам необходимо не просто так отправить данные на устройство, а отправить их с целью получения определенного ответа. Для того чтобы избежать вызова двух методов, в классе I2CDevice есть метод WriteRead. Он принимает два параметра в виде массива байтов. Первый массив содержит данные, которые вы собираетесь отправить на устройство, второй — буфер для данных из устройства.

Благодаря классу I2cDevice легко обмениваться данными с устройствами, но для того, чтобы получить ссылку на объект I2cDevice, вам нужно выполнить несколько задач.

Прежде всего, вам необходимо получить ссылку на I2C устройства. Microsoft использует тот же подход, что и для всех других устройств, таких как Bluetooth, WiFi и т.п. Вы должны использовать понятное имя для создания строки запроса для устройства и попытаться его найти. Метод GetdeviceSelector класса I2cDevice позволяет создать строку запроса. Для того, чтобы найти информацию о существующем устройстве вы должны использовать метод FindAllAsync класса DeviceInformation. Этот метод возвращает информацию о доступных I2C устройствах и вы можете использовать эту информацию для того, чтобы создать объект класса I2cDevice. На следующем шаге необходимо создать строку подключения к датчику. Это можно легко сделать с помощью класса I2cConnectionString. Если у вас есть информация об устройстве I2C и строка подключения внешнего устройства/датчика —  можно создать объект, используя метод FromIdAsync класса I2cDevice.

Таким образом, для моего MPU 6050я создал следующий код:

class MPU6050
{
   //I2C address
   private const byte MPUAddress = 0xD2>>1;
   private I2cDevice mpu5060device;

   public async Task BeginAsync()
   {
      string advanced_query_syntax = I2cDevice.GetDeviceSelector("I2C1");
      DeviceInformationCollection device_information_collection = await DeviceInformation.FindAllAsync(advanced_query_syntax);
      string deviceId = device_information_collection[0].Id;

      I2cConnectionSettings mpu_connection = new I2cConnectionSettings(MPUAddress);
      mpu_connection.BusSpeed = I2cBusSpeed.FastMode;
      mpu_connection.SharingMode = I2cSharingMode.Shared;

      mpu5060device = await I2cDevice.FromIdAsync(deviceId, mpu_connection);

      mpuInit();
   }
}

метод mpuInit отправляет начальные значения датчика (опишу его ниже). По документации MPUAddress должен быть 0xD2, но мы должны взять только 7 бит данного значения, поэтому я сместил значение на один бит вправо.

После того, как у нас появится объект класса I2cDevice, мы можем начать работать с устройством. Это не так то просто, так как у MPU 6050 есть множество регистров и вы должны понимать большинство из них. Кроме того, вы должны инициализировать датчик, чтобы получить значения. Давайте рассмотрим несколько регистров:

  • 0x6B — управление питанием. Можно устанавливать различные настройки, относящиеся к режиму питания, но самый важный бит под номером семь. Благодаря ему вы можете установить датчик в исходное состояние;
  • Данные акселерометра — 0x3B — 0x40. Есть 6 байт, которые содержат данные для осей х, у и z. Для каждой оси — 2 байта данных;
  • 0x41 — 0x42 — два байта, которые представляют температуру;
  • 0x43 — 0x48 — 6 байт для данных гироскопа (подобно акселерометру);

Таким образом, вы можете использовать метод mpuInit для настройки начального состояния датчика. Например, вы можете сбросить данные датчика с помощью следующей команды:

mpu5060device.Write(new byte[] { 0x6B, 0x80 });

Для того, чтобы измерить что-то  — используйте метод WriteRead. Что бы не городить кучу кода, в качестве примера работы с датчиками, я покажу как измерить температуру. Для этого вы можете использовать следующий код:

byte mpuRegRead(byte regAddr)<
{
   byte[] data=new byte[1];

   mpu5060device.WriteRead(new byte[] { regAddr },data);

   return data[0];
}

public double mpuGetTemperature()
{
   double temperature;
   short calc = 0
   byte []data = new byte[2];
   data[0] = mpuRegRead(MPU_REG_TEMP_OUT_H);//0x41
   data[1] = mpuRegRead(MPU_REG_TEMP_OUT_L);//0x42
   calc = (short)((data[0] << 8) | data[1]);

   temperature = (calc / 340.0) + 36.53;
   return temperature;
}

Позже я опубликую ссылку на дополнительные классы для работы с остальными сенсорами, написанные на C#.

Ссылка на источник: Windows 10: How to use IoT extension for Raspberry Pi 2 (part 2)

Tagged with: , , , ,
Опубликовано в Development, Windows 10

Windows 10: как использовать расширение IoT для Raspberry Pi 2 (часть 1)

Если вы уже установили Windows 10 на Raspberry — значит пришло самое время обсудить, как правильно использовать пины и I2C. Конечно, если вы собираетесь создавать приложение, которое не работает с GPIO, вы не должны делать что-то особенное — нужно попросту создать обычное универсальное приложение, такое же  как для настольного компьютера или телефона с использованием C# или других языков и задеплоить его на ARM платформы непосредственно из Visual Studio. Но GPIO устройства особенные, фичи которых нет смысла включать в общую библиотеку. Так что, если вы собираетесь использовать специфические особенности IoT необходимо добавить IoT расширение в ваш проект.

Это расширение описывает только два контракта: Windows.Devices.DevicesLowLevelContract и Windows.System.SystemManagementContract. Их легко найти по данному пути: C:\Program Files (x86)\Windows Kits\10\Extension SDKs\WindowsIoT\10.0.10069.0 и проверить SDKManifest.xaml. SystemManagement не содержит ничего особенного — просто классы, которые помогают выключать устройство и изменять часовой пояс. А вот DevicesLowLevelContract содержит много важных классов. Давайте посмотрим на Raspberry и поймем, чего же мы можем достичь благодаря этим классам.

В случае GPIO можно использовать желтые пины, чтобы посылать сигналы датчикам или принимать данные от них. В Raspberry все выводы генерируют напряжения в 3,3 вольта. Розовые контакты поддерживает протокол SPI, которые позволяют вам подключить до двух устройств/датчиков (возможно и три, но я никогда не пробовал так сделать). Наконец, синие пины позволяют использовать I2C концентратор, который поддерживает более ста устройств/датчиков. НУ и напоследок: GND пины для земли и еще четыре пина питания (два 5V и два 3,3V).

Начнем с GPIO. Вы можете найти необходимые классы и перечисления в пространстве имен Windows.Devices.Gpio, и они позволяют разработчикам контролировать GPIO пины. Первый класс это GpioController, который позволяет получить доступ к контроллеру и получить ссылку на необходимые контакты. Если вы собираетесь использовать GPIO, то вы должны начать с вызова метода GetDefault, который возвращает ссылку на текущий контроллер GPIO (если он есть):

var controller=GpioController.GetDefault();

Если вы собираетесь написать действительно универсальный код, то вам необходимо проверить контроллер на  null (убедиться в том, что он есть). Если это так (равно null) это значит, что у вашего устройства нет GPIO (дектоп, телефон и т.п.) Такого рода проверки очень полезны, т.к. начиная с Windows 10 у нас есть универсальная платформа, которую вы можете запустить на любом поддерживаемом устройстве.

Если у вас есть доступ к контроллеру, это значит, что вы можете попробовать получить ссылку на контакты. Метод OpenPin позволяет получить ссылку на штифт блокировки пина. Конечно, этот метод не поможет вам настроить пин и просто предоставить ссылку на объект GpioPin. Тут необходимо поступить так же, как поступали выше — проверить объект на null, потому что другие процессы могут заблокировать штифт для собственных целей.

var pin = controller.OpenPin(0);
if (pin == null)
{
     return;
}

Метод OpenPin должен получить номер пина. Вы должны использовать те же самые номера, как и на изображении выше. Наконец, если вы получили доступ к пину — вы можете попробовать работать с ним. Есть три важных метода: SetDriveMode, Read и Write.  С помощью первого Вы можете установить пин как вход или выход. Остальные два позволяют принимать и отправлять сигнал соответственно.

Так что, если вы уже работали с подобными устройствами — вы не найдете ничего особенного, но если вы новичок в IoT — вы можете начать с создания простого проекта Blinky от Microsoft, который вы можете найти здесь.

Необходимо признать, что наиболее интересные классы находятся в Windows.Devices.I2C и Windows.Devices.SPI пространствах имен. Но о них мы поговорим в следующий раз.

 

Ссылка на источник: Windows 10: How to use IoT extension for Raspberry Pi 2 (part 1)

Tagged with: , , , ,
Опубликовано в Development, Windows 10