Меня зовут Роман Гладких, я студент третьего курса Сибирского Государственного Университета Телекоммуникаций и Информатики по профилю Супервычисления. Так же являюсь студентом-партнером Майкрософт. Мое давнее хобби – это разработка приложений для Windows Phone и UWP на языке C#.
По умолчанию приложения UWP поддерживают две темы: темную (Dark) и светлую (Light). Так же имеется еще высококонтрастная тема (HighContrast). Такого набора обычно хватает для любого приложения, однако, что делать, если требуется быстро менять тему приложения на лету, причем ограничиваться Light и Dark нет желания?
В данном материале я расскажу, как реализовать свой менеджер тем. Материал ориентирован на новичков, однако и профессионалам может быть интересен. Милости просим под кат!
ThemeResource
Платформа UWP поддерживает специальное расширение разметки XAML, предназначение которого заключается в ссылке на ресурсы тем, которые могут обновляться во время выполнения. Ресурсы темы представляют собой набор ресурсов, применяющих различные значения в зависимости от того, какая тема системы активна.
{ThemeResource ResourceName}
Отличие от расширения разметки {StaticResource} в том, что {ThemeResource} может динамически использовать разные словари в качестве основного места поиска в зависимости от того, какая тема используется системой в данный момент. Другими словами, анализ значений, на которые ссылается {StaticResource} происходит только один раз при запуске приложения, тогда как {ThemeResource} при запуске и при каждом изменении темы системы.
Рассмотрим пример ResourceDictionary, в котором определяются пользовательские ресурсы темы.
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="MyBackgroundBrush"
Color="#FFFFFFFF" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="MyBackgroundBrush "
Color="#FF232323" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush x:Key="MyBackgroundBrush "
Color="#FF000000" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
В родительской ResourceDictionary в секции ThemeDictionaries объявлены дочерние библиотеки, которые и являются наборами ресурсов для каждой из тем. В каждой библиотеке объявлена кисть с одним названием, но разным значением Color.
Итого, если мы будем ссылаться на нашу кисть при помощи {ThemeResource}, например, зададим прямоугольнику эту кисть как заливку, то в зависимости от выбранной в системе темы, мы получим прямоугольник белого, серого или черного цвета.
Обратите внимание, что в ресурсах темы могут лежать не только кисти, но также строки и другие объекты. Чтобы разработчик мог ознакомиться со всеми системными ресурсами темы, в Windows SDK входит XAML-файл, содержащий все ресурсы. Расположен он в C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\\Generic\ themeresources.xaml.
Как разработать свой менеджер тем?
Взвесив все за и против, мы пришли к выводу, что хотим больше, хотим менять их на лету и не зависеть от системной темы. Как это реализовать?
Так как в платформе UWP отсутствует расширение разметки {DynamicResource}, который, к слову, имеется в WPF, довольствоваться будем обычными привязками {Binding}.
Для начала создадим проект пустого приложения UWP с имененем UwpThemeManager. Минимальной версией я установил Windows 10 Anniversary Update, целевой Windows 10 Creators Update.
В проекте создадим папку Themes, внутри два ResourceDictionary с именами Theme.Dark.xaml и Theme.Light.xaml.
В каждом файле добавим в ResourceDictionary три кисти с именами BackgroundBrush, ForegroundBrush и ChromeBrush. Содержимое этих файлов доступно под спойлерами.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="BackgroundBrush"
Color="#FF1A1A1A" />
<SolidColorBrush x:Key="ForegroundBrush"
Color="White" />
<SolidColorBrush x:Key="ChromeBrush"
Color="#FF232323" />
</ResourceDictionary>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="BackgroundBrush"
Color="White" />
<SolidColorBrush x:Key="ForegroundBrush"
Color="Black" />
<SolidColorBrush x:Key="ChromeBrush"
Color="#FFBFBFBF" />
</ResourceDictionary>
Теперь нам потребуется специальный класс, который будет загружать ресурсы наших тем и уведомлять все Binding об изменении ссылок на кисти. Создадим запечатанный (sealed) класс ThemeManager, реализующий интерфейс INotifyPropertyChanged.
public sealed class ThemeManager : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Обязательно добавим в класс публичные строковые константы с путем до наших словарей с темами.
public const string DarkThemePath = "ms-appx:///Themes/Theme.Dark.xaml";
public const string LightThemePath = "ms-appx:///Themes/Theme.Light.xaml";
В код нашего класса добавим приватное поле типа ResourceDictionary – это будет словарь с текущими значениями темы.
private ResourceDictionary _currentThemeDictionary;
Далее требуется добавить в класс ThemeManager свойства типа Brush, чтобы не допускать ошибок при биндинге из XAML, и работали подсказки от Visual Studio. Во избежание путаницы, назовем свойства точно так же, как кисти названы в словарях тем. Так же для нашего удобства добавим строковое свойство CurrentTheme, которое будет возвращать имя текущей темы.
public string CurrentTheme { get; private set; }
public Brush BackgroundBrush => _currentThemeDictionary[nameof(BackgroundBrush)] as Brush;
public Brush ChromeBrush => _currentThemeDictionary[nameof(ChromeBrush)] as Brush;
public Brush ForegroundBrush => _currentThemeDictionary[nameof(ForegroundBrush)] as Brush;
Чтобы при смене темы все привязки {Binding} узнали о том, что ссылки на кисти поменялись, нужно вызвать событие PropertyChanged для каждого из свойств. Создадим для этого специальный приватный метод.
Чтобы не допустить ошибки в названиях свойств, используем ключевое слово nameof, вызов которого при компиляции преобразуется в строковую константу с именем указанного элемента.
private void RaisePropertyChanged()
{
OnPropertyChanged(nameof(BackgroundBrush));
OnPropertyChanged(nameof(ChromeBrush));
OnPropertyChanged(nameof(ForegroundBrush));
OnPropertyChanged(nameof(CurrentTheme));
}
Теперь встает вопрос о загрузке словарей с темами. Создадим два метода: LoadTheme и LoadThemeFromFile. Первый метод загружает словарь с темой, расположенный в пакете приложения (для этого выше мы задали константы DarkThemePath и LightThemePath). Второй метод загружает тему из любого файла (принимает на вход StorageFile), не обязательно из пакета приложения.
Реализация методов занимает несколько строк.
public void LoadTheme(string path)
{
_currentThemeDictionary = new ResourceDictionary();
App.LoadComponent(_currentThemeDictionary, new Uri(path));
CurrentTheme = Path.GetFileNameWithoutExtension(path);
RaisePropertyChanged();
}
public async Task LoadThemeFromFile(StorageFile file)
{
string xaml = await FileIO.ReadTextAsync(file);
_currentThemeDictionary = XamlReader.Load(xaml) as ResourceDictionary;
CurrentTheme = Path.GetFileNameWithoutExtension(file.Path);
RaisePropertyChanged();
}
ThemeManager почти готов, осталось лишь добавить в конструктор вызов метода загрузки темной темы (она будет по умолчанию).
public ThemeManager()
{
LoadTheme(DarkThemePath);
}
Все готово! Осталось объявить экземпляр нашего класса в App.xaml в секции ресурсов приложения и добавить статическую ссылку На этот экземпляр в App.xaml.cs.
<Application x:Class="UwpThemeManager.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UwpThemeManager">
<Application.Resources>
<ResourceDictionary>
<local:ThemeManager x:Key="ThemeManager" />
</ResourceDictionary>
</Application.Resources>
</Application>
public static ThemeManager ThemeManager
=> (ThemeManager)App.Current.Resources["ThemeManager"];
Полный код ThemeManager.cs представлен под спойлером.
using System;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;
namespace UwpThemeManager
{
public sealed class ThemeManager : INotifyPropertyChanged
{
public const string DarkThemePath = "ms-appx:///Themes/Theme.Dark.xaml";
public const string LightThemePath = "ms-appx:///Themes/Theme.Light.xaml";
public event PropertyChangedEventHandler PropertyChanged;
public ThemeManager()
{
LoadTheme(DarkThemePath);
}
public string CurrentTheme { get; private set; }
public Brush BackgroundBrush => _currentThemeDictionary[nameof(BackgroundBrush)] as Brush;
public Brush ChromeBrush => _currentThemeDictionary[nameof(ChromeBrush)] as Brush;
public Brush ForegroundBrush => _currentThemeDictionary[nameof(ForegroundBrush)] as Brush;
public void LoadTheme(string path)
{
_currentThemeDictionary = new ResourceDictionary();
App.LoadComponent(_currentThemeDictionary, new Uri(path));
CurrentTheme = Path.GetFileNameWithoutExtension(path);
RaisePropertyChanged();
}
public async Task LoadThemeFromFile(StorageFile file)
{
string xaml = await FileIO.ReadTextAsync(file);
_currentThemeDictionary = XamlReader.Load(xaml) as ResourceDictionary;
CurrentTheme = Path.GetFileNameWithoutExtension(file.Path);
RaisePropertyChanged();
}
private void RaisePropertyChanged()
{
OnPropertyChanged(nameof(BackgroundBrush));
OnPropertyChanged(nameof(ChromeBrush));
OnPropertyChanged(nameof(ForegroundBrush));
OnPropertyChanged(nameof(CurrentTheme));
}
private void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private ResourceDictionary _currentThemeDictionary;
}
}
Использование менеджера тем
Так как мы выполнили все необходимые приготовления, использование ThemeManager будет очень простым. Рассмотрим небольшой пример.
<Rectangle Fill="{Binding BackgroundBrush, Source={StaticResource ThemeManager}}"/>
В данном примере мы объявили элемент Rectangle (прямоугольник), у которого свойство Fill (заливка) привязали к свойству BackgroundBrush из ThemeManager, расположенного в ресурсах приложения.
Создадим простую страницу MainPage (в новом проекте уже имеется). Итоговая страница будет так:
Задайте для кнопок и других элементов управления необходимые привязки к нашим кистям. В обработчиках события клика для кнопок выполним загрузку других тем.
private void DarkThemeButton_Click(object sender, RoutedEventArgs e)
=> App.ThemeManager.LoadTheme(ThemeManager.DarkThemePath);
private void LightThemeButton_Click(object sender, RoutedEventArgs e)
=> App.ThemeManager.LoadTheme(ThemeManager.LightThemePath);
private async void CustomThemeButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".xaml");
var file = await picker.PickSingleFileAsync();
if (file != null)
{
try
{
await App.ThemeManager.LoadThemeFromFile(file);
}
catch (Exception ex)
{
var msg = new MessageDialog(ex.ToString(), "Ошибка");
await msg.ShowAsync();
}
}
}
Для первых двух кнопок мы вызывает метод LoadTheme в ThemeManager с указанием константы с путем до файла XAML с темой. Последний обработчик события (у кнопки с текстом Custom theme) создает окно выбора файла, указывает фильтр по типу .xaml и показывает пользователю стандартное окно выбора файла. Если пользователь выбрал файл, то он передается в метод LoadThemeFromFile, который мы реализовали в ThemeManager.
Для тестирования, создайте третий файл темы, и разместите его, например, на рабочем столе. Мой вариант:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="BackgroundBrush"
Color="#FF1A1A1A" />
<SolidColorBrush x:Key="ForegroundBrush"
Color="White" />
<SolidColorBrush x:Key="ChromeBrush"
Color="#FF5A0000" />
</ResourceDictionary>
Скомпилируйте и запустите приложение. При нажатии на кнопки Dark theme и Light theme, цветовое оформление приложения будет автоматически меняться. Нажмите на кнопку Custom theme, затем откройте файл Theme.Red.xaml. Цветовое оформление приложения станет красным.
Полный исходный код разметки страницы под спойлером.
<Page x:Class="UwpThemeManager.MainPage1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{Binding BackgroundBrush, Source={StaticResource ThemeManager}}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition />
</Grid.RowDefinitions>
<Border Background="{Binding ChromeBrush, Source={StaticResource ThemeManager}}">
<TextBlock Text="{Binding CurrentTheme, Source={StaticResource ThemeManager}}"
Foreground="{Binding ForegroundBrush, Source={StaticResource ThemeManager}}"
Style="{StaticResource SubtitleTextBlockStyle}"
VerticalAlignment="Center"
Margin="12,0,0,0" />
</Border>
<StackPanel Grid.Row="1"
HorizontalAlignment="Center">
<Button Content="Dark theme"
Background="{Binding ChromeBrush, Source={StaticResource ThemeManager}}"
Foreground="{Binding ForegroundBrush, Source={StaticResource ThemeManager}}"
Margin="0,12,0,0"
HorizontalAlignment="Stretch"
Click="DarkThemeButton_Click" />
<Button Content="Light Theme"
Background="{Binding ChromeBrush, Source={StaticResource ThemeManager}}"
Foreground="{Binding ForegroundBrush, Source={StaticResource ThemeManager}}"
Margin="0,12,0,0"
HorizontalAlignment="Stretch"
Click="LightThemeButton_Click" />
<Button Content="Custom theme"
Background="{Binding ChromeBrush, Source={StaticResource ThemeManager}}"
Foreground="{Binding ForegroundBrush, Source={StaticResource ThemeManager}}"
Margin="0,12,0,0"
HorizontalAlignment="Stretch"
Click="CustomThemeButton_Click" />
</StackPanel>
</Grid>
</Page>
Подводные камни
Если задавать значения Background, Foreground и т.д. у самих элементов, то все будет работать, однако мы не можем задать {Binding} в стилях элементов управления. В UWP привязки в Style не поддерживаются. Как же это обойти? Нам поможет Attached DependencyProperty!
Attached Property. Это Dependency Property, которое объявлено не в классе объекта, для которого оно будет использоваться, но ведет себя, как будто является его частью. Объявляется в отдельном классе, имеет getter и setter в виде статических методов. Можно добавить обработчик на событие PropertyChanged.
Подробнее про Attached property вы можете узнать немного подробнее в статье AndyD: WPF: использование Attached Property и Behavior
Реализуем Attached property для свойств Background и Foreground. Это будут статические классы с именем BackgroundBindingHelper и ForegroundBindingHelper. Объявим статические методы GetBackground (возвращает string) и SetBackground, а также DependencyProperty с типом значения string.
В Visual Studio имеется специальная заготовка (code snippet) для Attached Dependency Property, которая доступна, если ввести propa и нажать Tab.
Так же добавим приватный метод-обработчик BackgroundPathPropertyChanged, который будет обновлять Binding при изменении значения Background.
ForegroundBindingHelper реализуется аналогичным образом.
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace UwpThemeManager.BindingHelpers
{
public static class BackgroundBindingHelper
{
public static string GetBackground(DependencyObject obj)
=> (string)obj.GetValue(BackgroundProperty);
public static void SetBackground(DependencyObject obj, string value)
=> obj.SetValue(BackgroundProperty, value);
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.RegisterAttached("Background", typeof(string), typeof(BackgroundBindingHelper),
new PropertyMetadata(null, BackgroundPathPropertyChanged));
private static void BackgroundPathPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var propertyPath = e.NewValue as string;
if (propertyPath != null)
{
var backgroundproperty = Control.BackgroundProperty;
BindingOperations.SetBinding(obj, backgroundproperty, new Binding
{
Path = new PropertyPath(propertyPath),
Source = App.ThemeManager
});
}
}
}
}
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
namespace UwpThemeManager.BindingHelpers
{
public static class ForegroundBindingHelper
{
public static string GetForeground(DependencyObject obj)
=> (string)obj.GetValue(ForegroundProperty);
public static void SetForeground(DependencyObject obj, string value)
=> obj.SetValue(ForegroundProperty, value);
public static readonly DependencyProperty ForegroundProperty =
DependencyProperty.RegisterAttached("Foreground", typeof(string),
typeof(ForegroundBindingHelper), new PropertyMetadata(null, ForegroundPathPropertyChanged));
private static void ForegroundPathPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var propertyPath = e.NewValue as string;
if (propertyPath != null)
{
var backgroundproperty = Control.ForegroundProperty;
BindingOperations.SetBinding(obj, backgroundproperty, new Binding
{
Path = new PropertyPath(propertyPath),
Source = App.ThemeManager
});
}
}
}
}
Отлично! Теперь мы можем биндиться к нашим кистям даже в стилях. Для примера создадим стиль для кнопок на нашей странице.
<Page.Resources>
<Style x:Key="ButtonStyle"
TargetType="Button">
<Setter Property="binding:BackgroundBindingHelper.Background"
Value="ChromeBrush" />
<Setter Property="binding:ForegroundBindingHelper.Foreground"
Value="ForegroundBrush" />
<Setter Property="Margin"
Value="0,12,0,0" />
<Setter Property="HorizontalAlignment"
Value="Stretch"/>
</Style>
</Page.Resources>
В Setter.Property указано имя класса, которое предоставляет AttachedProperty. В Value указано имя свойства с кистью из ThemeManager.
Задайте этот стиль кнопкам на странице, и все будет работать так же хорошо, как и при прямом указании Background и Foreground элементам. Итоговый исходный код разметки под спойлером.
<Page x:Class="UwpThemeManager.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:binding="using:UwpThemeManager.BindingHelpers"
mc:Ignorable="d">
<Page.Resources>
<Style x:Key="ButtonStyle"
TargetType="Button">
<Setter Property="binding:BackgroundBindingHelper.Background"
Value="ChromeBrush" />
<Setter Property="binding:ForegroundBindingHelper.Foreground"
Value="ForegroundBrush" />
<Setter Property="Margin"
Value="0,12,0,0" />
<Setter Property="HorizontalAlignment"
Value="Stretch"/>
</Style>
</Page.Resources>
<Grid Background="{Binding BackgroundBrush, Source={StaticResource ThemeManager}}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition />
</Grid.RowDefinitions>
<Border Background="{Binding ChromeBrush, Source={StaticResource ThemeManager}}">
<TextBlock Text="{Binding CurrentTheme, Source={StaticResource ThemeManager}}"
Foreground="{Binding ForegroundBrush, Source={StaticResource ThemeManager}}"
Style="{StaticResource SubtitleTextBlockStyle}"
VerticalAlignment="Center"
Margin="12,0,0,0" />
</Border>
<StackPanel Grid.Row="1"
HorizontalAlignment="Center">
<Button Content="Dark theme"
Style="{StaticResource ButtonStyle}"
Click="DarkThemeButton_Click" />
<Button Content="Light Theme"
Style="{StaticResource ButtonStyle}"
Click="LightThemeButton_Click" />
<Button Content="Custom theme"
Style="{StaticResource ButtonStyle}"
Click="CustomThemeButton_Click" />
</StackPanel>
</Grid>
</Page>
Подведем итоги
Путем нехитрых манипуляций мы реализовали свой собственный менеджер тем, который не зависит от системной, и может работать с любым количеством тем, в том числе подгружать сторонние.
Полный исходный код проекта доступен на GitHub: ссылка.
Надеюсь, статья вам понравилась. Если нашли какую-либо неточность или ошибку, не стесняйтесь написать мне в личные сообщения.
До встречи на просторах Хабрахабра!
Комментарии (9)
denismaster
23.08.2017 00:38+2Солидарен с комментарием выше. Технология безусловно хорошая, но, к сожалению, не популярна. В основном это связано с малым числом поддерживаемых платформ.
В частности, очень популярно создавать Windows приложения на WPF, которая поддерживается на всех ОС от Microsoft, начиная от XP, если не ошибаюсь. А UWP поддерживает только десятку, и то, не все редакции(некоторые вещи только в Creators Update появляются, например).
Для развития UWP надо расширить круг поддерживаемых платформ, бэкпортировать ее на Windows 7, 8, обеспечить работу на Mac OS / Linux. Там ведь и так .NET Core под капотом, если не память не изменяет.
Увы, именно поэтому разработчики выбирают Electron, а не UWP.
dmitry_dvm
23.08.2017 12:59А где сохраняется выбранная тема? Ведь по дефолту грузится всегда темная. Создавать свойство в Roaming?
RomanGL Автор
23.08.2017 13:03В данном примере тема нигде не сохраняется. Если тема из комплекта приложения, то сохранять ее имя/путь в RoamingSettings и загружать оттуда при запуске.
В противном случае, копировать xaml со сторонней темой в RoamingFolder, так же сохранить имя/путь в RoamingSettings.
akrafts
23.08.2017 15:48Стоит упомянуть что Майкрософт рекомендует добавлять в секцию по выбору тем пункт «по умолчанию в системе»
AgentFire
23.08.2017 18:11Я как то делал, помню, что инвок события OnPropertyChanged(string.Empty или null, не помню) вызывается сразу для всех свойств объекта, и не нужно вручную их все перебирать.
jknight
Сейчас, наверное, заминусуете… Однако, хотелось бы услышать долю инсайда от человека, который близок к MS. Вопрос только один. Как заставить разработчиков это использовать? Нет, API хорошее, но на сказке о порочном круге «нет пользователей — нет разработчиков» уже не одну собаку съели. А сама мысль тач-подобных приложений на десктопе меня в ужас приводит :)
Пытался, честно, использовать самые разные приложения — почта, календарь, тот же калькулятор, Skype, Twitter, и т.д. — все это попросту неудобно на мыши и клавиатуре. Превращение приложений из многофункциональных в однокнопочные — бабушке полезно, но не мне.
Вчера попробовал поставить себе другую тему Windows из магазина. Скачалась как приложение, оказалась в Пуске, не запустилась, окошки остались прежними. Ну вот кто будет пользоваться таким магазином?
У меня четвертый год Windows Phone — с 520 до 930. По последнему опыту готов сказать, что, несмотря на все потуги Майкрософта в области UWP, эту технологию попросту некому использовать — старые мобилки выброшены из сопровождения, новых не появляется. Мобильный мир, судя по feature2(кто знает — поймет), заморожен и убит, а десктопный — мертворожденно.
d2funlife
Когда то писал у себя заметку тут. Основной причиной, почему не пошел UWP — нет единой экосистемы. Это лишь еще что-то со стороны. Есть десктоп и UWP, но они разные по своей сути и нет общего. Те же настройки UWP приложение и панель управления, которые в 10-ке, как пример не стыковки разных «миров».
dmitry_dvm
А по-моему на десктопе очень удобно. Пользуюсь почтой, эджем, mytube, дефолтным плеером. На мой взгляд крупные элементы удобнее в повседневном использовании. Понятно, что профессиональные инструменты не очень удобны с таким интерфейсом, но тут и цель другая. А еще очень радует что одни и те же приложения на телефоне и компе. Как разработчика и как юзера. Все плюсы ощущаются на тач-экранах. Ждем арм-винду, посмотрим, что будет.
Чем калькулятор новый отличается от старого?
IL_Agent
Увы, непонятно, зачем нужна uwp, когда мобилки на винде мертвы. Под десктоп и так есть на чем разрабатывать приложения, и работать они будут не только в 10-ке, не только в песочнице и не только через магазин. Да и в магазин можно публиковать не только uwp.