Архив рубрики: Software

WPF: Использование WinRT API

Для моей программы на WPF мне потребовалось добавить системные Toast-уведомления в Windows 10+. В принципе для C# есть официальный мануал для подобных уведомлений: Send a local toast notification from a C# app. Проблема в том, что для этого необходимо, чтобы проект был UWP, но мне он не подходит, т.к. для моего проекта нужны права администратора, чего нет в UWP (к тому же какое-то время ходят слухи о закрытии Windows Store и соответственно прекращении поддержки UWP). Может что-то и получилось бы с WinUI 3, но вроде пока здесь тоже возникают какие-то проблемы с повышением привилегий…
Учитывая всё вышесказанное, продолжу использовать WPF.

Сначала я решил подключить NuGet пакет Notifications.Wpf, но в итоге оказалось, что во-первых проект необоснованно сложный (мне в приложении нужно вывести лишь пару уведомлений), а во вторых видео-инструкция к нему обрывается на середине, так что даже просто разобраться в коде уже проблема.

Попробую показывать уведомления через вызов Windows 10 API. Базовую статью я нашёл в этом блоге. Судя по доменному имени, это официальный блог Microsoft, однако качество самого блога удручает — взять хотя бы разметку блоков с кодом. Ладно, попробую так разобраться…

Старая версия

1. Перед тем, как создавать проект, нужно добавить новые компоненты в Visual Studio, если такие пока не установлены:
Visual Studio InstallerWorkloadsUniversal Windows Platform development
2. Через меню добавим ссылку на Windows.winmd:
Project] ▶ Add Project Reference…Browse…

"C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.22000.0\Windows.winmd"

3. Установим WindowsRuntime
ToolsNuGet Package MaganerPackage Manager Console

Package Manager Console
NuGet\Install-Package System.Runtime.WindowsRuntime

4. Дополнительно нужно установить UwpDesktop:

Package Manager Console
NuGet\Install-Package UwpDesktop

Если сейчас всё оставить как есть, то при запуске получим ошибку

Error NETSDK1130 Windows.winmd cannot be referenced. Referencing a Windows Metadata component directly when targeting .NET 5 or higher is not supported. For more information, see https://aka.ms/netsdk1130

 

Чтобы её избежать, переходим к п.5.

 

5. Добавим ещё один компонент:

Package Manager Console
dotnet add package Microsoft.Windows.CsWinRT

Всё равно я получал кучу ошибок ещё на этапе проверки синтаксиса, например:

The type forwarder for type ‘Windows.Data.Xml.Dom.XmlDocument’ in assembly ‘Windows’ causes a cycle at DuckDuckGo

CS0234 The type or namespace name ‘System’ does not exist in the namespace ‘Windows’ (are you missing an assembly reference?) — Google Search

CS0234 The type or namespace name ‘Devices’ does not exist in the namespace ‘Windows’ (are you missing an assembly reference?) at DuckDuckGo

Рабочая версия GeoLocation Demo App

Я нашёл видео-демонстрацию возможностей WinRT как раз с таким примером, его и переделал.

GeoLocation Demo App:

Я немного переделал код из блога.

  1. Поскольку для доступа к WinRT API выставлены довольно жёсткие требования к версии ОС и .NET, то нам необходимо их выставить в свойствах проекта:
    ProjectPropertiesApplicationGeneral
    Target framework: .NET 6.0
    Target OS: Windows
    Target OS version: 10.0.17763.0
    Windows Presentation Foundation: Enable WPF for this project.

    GeolocationDemoApp.csproj
    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
        <RootNamespace>GeolocationDemoApp</RootNamespace>
        <Nullable>enable</Nullable>
        <UseWPF>True</UseWPF>
      </PropertyGroup>
    
    </Project>

    Если этого не сделать, то компилятор выдаст ошибку:

    Error CS0246 The type or namespace name ‘Windows’ could not be found (are you missing a using directive or an assembly reference?)
    GeolocationDemoApp C:\Users\Denis\source\repos\GeolocationDemoApp\MainWindow.xaml.cs

     

  2. В Дизайнере в окно программы добавим лишь одну кнопку:
    MainWindow.xaml
    <Window x:Class="GeolocationDemoApp.MainWindow"
            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:local="clr-namespace:GeolocationDemoApp"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Grid>
            <Button Content="Button"
                    Width="200"
                    Height="50"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Click="Button_Click"/>
            <TextBlock x:Name="geoText"
                HorizontalAlignment="Center"
                       Margin="0,291,0,0"
                       TextWrapping="Wrap"
                       TextAlignment="Center"
                       Text="TextBlock"
                       VerticalAlignment="Top"
                       Width="200"
                       FontSize="24"
                       Background="#FFEFEED6"
                       />
        </Grid>
    </Window>
  3. За логику кнопки будет отвечать следующий код:
    MainWindow.xaml.cs
    using System;
    using System.Windows;
    
    namespace GeolocationDemoApp
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private async void Button_Click(object sender, RoutedEventArgs e)
            {
                //throw new NotImplementedException();
                var locator = new Windows.Devices.Geolocation.Geolocator();
                var location = await locator.GetGeopositionAsync();
                var position = location.Coordinate.Point.Position;
                var latlong = string.Format("lat:{0}, long:{1}",
                                            position.Latitude, position.Longitude);
                //var result = MessageBox.Show(latlong);
                geoText.Text = latlong;
            }
        }
    }
  4. Мы не включили в код обработку исключений. Поэтому, если на уровне системы запрещён доступ к местоположению, выскочит ошибка. А если запустить без отладки, никакого окошка не появится, поэтому нужно дать системе разрешение на доступ к местоположению:
    SettingsPrivacy ▶ [App permissions] ▶ LocationAllow access to location on this deviceChangeOn
    После этого нужно разрешить приложениям доступ к местоположению:
    SettingsPrivacy ▶ [App permissions] ▶ LocationAllow apps to access your locationOn
  5. В результате, при нажатии на кнопку будут выведены долгота и широта текущего местоположения, переданного системой:
    Координаты