January 2018
Volume 33 Number 1
ユニバーサル Windows プラットフォーム - UWP を使用して基幹業務アプリケーションを作成する
ある日、上司があなたの元に来て新しい仕事を命じます。あなたは、Windows 用の新しい基幹業務ビジネス (LOB) アプリケーションを作成しなければなりません。Web はオプションに入りません。アプリケーションを 10 年以上使用できる最適なプラットフォームを選択する必要があります。
これは難しい選択のように思えます。選択肢には Windows フォームや Windows Presentation Foundation (WPF) があります。これらは多数のコンポーネントを備える成熟したテクノロジで、.NET 開発者のあなたは、これを使用する豊富な知識と経験があります。でも、これらのテクノロジは 10 年後も使用されているでしょうか。そうなるだろうとは推測できます。近い将来、マイクロソフトがこれらを廃止する気配はありません。しかし、やや古い (Windows フォームは 2001 年に登場、WPF は 2006 年に作成) ことに加え、これらのテクノロジにはいくつか問題があります。Windows フォームは最新のグラフィックス カードに対応しておらず、追加のコンポーネントを使用したり、テクニックを駆使しなければ、昔から変わらない退屈なスタイルに縛られることになります。WPF はいまだに更新されているとはいえ、OS やその SDK に対する最新の機能強化には追い付いていません。さらに、どちらも Microsoft Store に配置できないため、そのメリットを得ることはできません。つまり、配置やインストール、アンインストールを簡単に行う方法は存在せず、全世界のユーザー向けに配布することなども容易ではありません。Centennial を使用してアプリケーションをパッケージ化することはできますが、それでは "本物" になりません。
もう少し調査を進めたところ、Windows 10 向けの開発用ユニバーサル Windows プラットフォーム (UWP) が見つかり、これには Windows フォームや WPF のような欠点がないことがわかりました。これには積極的な強化が加えられており、小型の Raspberry Pi から超大型の Surface Hub まで、多様なデバイスで変更することなく使用できます。しかし、UWP は、Windows タブレットやスマートフォン向けの小さなアプリケーションを開発するためのもので、UWP を使用する LOB アプリケーションなど考えられない、だれもが口をそろえてこう言います。それに加えて、学習や開発が非常に難しいと言います。たとえば、すべての新しいパターン、XAML 言語、実行内容を制限するサンドボックスなど、他にもまだまだあります。
こうした指摘はすべて現実とはかけ離れています。過去の UWP にはある程度の制約があり、学習や使用が難しかったこともあります。しかし、それはもう事実ではありません。新機能の開発が積極的に行われています。新しい Fall Creators Update (FCU) とその SDK では、SQL Server に直接アクセスできるだけなく、.NET Standard 2.0 の API さえ使用できます。同時に、新しいツールとコンポーネントにより、新しいアプリケーションを開発するための最高のエクスペリエンスが提供されます。Visual Studio の拡張機能である Windows Template Studio (WTS) は、フル機能を備えた UWP アプリケーションの開発をすばやく始める方法を提供します。さらに、最高の UX を実現するために、UWP 用 Telerik コンポーネントを無料で使用できます。悪くないとは思いませんか。現在は、UWP LOB アプリケーションを開発するための新しいプラットフォームになっています。
今回は、WTS、Telerik コンポーネント、.NET Standard 2.0 による SQL Server への直接アクセスを使用して、UWP LOB アプリケーションを開発します。そのため、すべてのしくみを直接確認できます。
Windows Template Studio の概要
前述のように、.NET Standard 2.0 を使用するには、FCU と対応する SDK が必要です。コンピューターに FCU がインストールされていても (バージョンがわからない場合は、Windows キーを押しながら R キーを押し、「Winver」と入力することでお使いの Windows のバージョンが表示されます。そのバージョンは 1709 以降であることが必要です)、UWP での .NET Standard 2.0 のサポートは、Visual Studio 2017 Update 4 以降でのみ利用できます。そのため、更新してない場合は今すぐ行ってください。また、.NET Standard 2.0 を使用するには .NET 4.7 ランタイムもインストールされている必要があります。
インフラストラクチャの準備が整ったら、WTS をインストールします。Visual Studio で、[ツール]、[拡張機能と更新プログラム] の順に移動し、「Windows Template Studio」を検索します。ダウンロードしたら、Visual Studio を再起動することでインストールが完了します。また、aka.ms/wtsinstall (英語) にアクセスし、インストーラーをダウンロードすることでも WTS をインストールできます。
Windows Template Studio は、UWP 開発の優秀かつ簡単な出発点となる、オープン ソースの新しい拡張機能です (ソース コードは aka.ms/wts (英語) から入手できます)。開発者は、プロジェクトの種類 (ハンバーガー メニューを備えたナビゲーション ウィンドウ、空のプロジェクト、またはピボットとタブ)、Model-View-ViewModel (MVVM) パターン用の MVVM フレームワーク、プロジェクトに含めるページ、および組み込むことを考えている Windows 10 機能を選ぶことができます。このプロジェクトでは多くの開発が進行中であり、Prism、Visual Basic テンプレート、および Xamarin サポートが夜間ビルドで既に利用できます (安定バージョンは近日公開予定です)。
WTS がインストールされていれば、Visual Studio に移動して、[ファイル]、[新しいプロジェクト]、[Windows ユニバーサル] の順に選択し、[Windows Template Studio] を選ぶことで新しいプロジェクトを作成できます。プロジェクトを選択する新しいウィンドウが開きます (図 1 参照)。
図 1 Windows Template Studio のメイン画面
[Navigation Pane] (ナビゲーション ウィンドウ) プロジェクトと [MVVM Light] (MVVM ライト) フレームワークを選択し、[Next] (次へ) を選びます。ここで、アプリケーションのページを選択します (図 2 参照)。
図 2 ページ選択
右側の [Summary] (概要) 列で確認できるように、既に "Main" という空のページが選択されています。[Settings] (設定) ページを選択して、「Settings」という名前を付けます。[Master/Detail] (マスター/詳細) ページを選択して、「Sales」という名前を付けます。その後、[Next] (次へ) をクリックします。次のウィンドウでは、ライブ タイルやトースト、アプリケーションを初めて実行したときに表示する初期設定ダイアログなど、アプリケーションの機能をいくつか選択します。ここでは、[Settings] (設定) ページを選ぶことで選択される設定の保存場所を除いて機能を選択しません。
ここで、[Create] (作成) をクリックすると、選択した機能を備えた新しいアプリケーションが作成されます。この時点でアプリケーションは実行できます。これはハンバーガー メニュー、2 つのページ (空のページとマスター/詳細ページ)、アプリケーションのテーマを変更できる設定ページ (下部にある歯車アイコンをクリックしてアクセスします) を備えた、完全なアプリケーションです。
ソリューション エクスプローラーに移動すると、Models、ViewModels、Services など、このアプリケーション用に多数のフォルダーが作成されているのがわかります。文字列のローカライズ用に Strings フォルダーまで存在し、en-US サブフォルダーもあります。アプリケーションに他の言語を追加するには、この Strings フォルダーに新しいサブフォルダーを追加し、その名前を目的の言語のロケール (fr-FR など) に変更します。次に、Resources.resw ファイルをコピーして、そのファイルを新しい言語で翻訳します。それだけです。わずか数回のクリックでこれだけのことができるとは感動的ではありませんか。しかし、上司がこの仕事を命じたときに期待していたものがこれでないことはわかっています。そこで、このアプリケーションをカスタマイズしていきます。
アプリケーションから SQL Server にアクセスする
FCU と Visual Studio Update 4 で導入された優れた機能の 1 つが、UWP での .NET Standard 2.0 のサポートです。これはすばらしい強化です。というのも、これにより、SQL Server クライアント アクセスや Entity Framework Core など、以前は UWP アプリケーションで使用できなかった多数の API が使用できるようになります。
SQL Server クライアント アクセスをアプリケーションで利用できるようにするには、アプリケーションのプロパティに移動し、[アプリケーション] タブで [最小バージョン] として [Build 16299] (ビルド 16299) を選択します。次に、[参照] ノードを右クリックして、[NuGet パッケージの管理] を選択して、System.Data.SqlClient をインストールします。これで、ローカル SQL Server データベースにアクセスする準備が整いました。1 つ注意点として、このアクセスを行うには、既定のアクセス方法である名前付きパイプではなく、TCP/IP を使用します。そのため、SQL Server 構成アプリケーションを実行して、サーバー インスタンスの TCP/IP 接続を有効にする必要があります。
また、UWP グリッド用の Telerik UI も使用します。現時点ではこれは無料のオープン ソース製品で、bit.ly/2AFWktT (英語) から見つかります。そこで、NuGet パッケージ マネージャー ウィンドウがまだ開いているうちに、Telerik.UI.for.UniversalWindowsPlatform パッケージを選択し、インストールします。WTS でグリッド ページを追加している場合、このパッケージは自動的にインストールされます。
このアプリケーションでは、WorldWideImporters サンプル データベースを使用します。このデータベースは bit.ly/2fLYuBk (英語) からダウンロードして、SQL Server インスタンスに復元できます。
ここで、既定のデータ アクセスを変更しなければなりません。Models フォルダーに移動すると、先頭に次のコメントが記述されている SampleOrder クラスを確認できます。
// TODO WTS: Remove this class once your pages/features are using your data.
// This is used by the SampleDataService.
// It is the model class we use to display data on pages like Grid, Chart, and Master Detail.
実行手順についてのガイダンスを提供する多数のコメントが、このプロジェクト全体に配置されています。今回の場合、次に示すような Order クラスが必要になります。クラスの名前を「Order」に変更して、図 3 のようなクラスになるように変更を加えます。
図 3 Order クラス
public class Order : INotifyPropertyChanged
{
public long OrderId { get; set; }
public DateTime OrderDate { get; set; }
public string Company { get; set; }
public decimal OrderTotal { get; set; }
public DateTime? DatePicked { get; set; }
public bool Delivered => DatePicked != null;
private IEnumerable<OrderItem> _orderItems;
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<OrderItem> OrderItems
{
get => _orderItems;
set
{
_orderItems = value;
PropertyChanged?.Invoke(
this, new PropertyChangedEventArgs("OrderItems"));
}
}
public override string ToString() => $"{Company} {OrderDate:g} {OrderTotal}";
}
このクラスは INotifyPropertyChanged インターフェイスを実装します。注文を表示するときに必要に応じて注文品を読み込めるように、注文品が変更されたら UI に通知する必要があるためです。注文品を保存するために、別のクラス OrderItem を定義しています。
public class OrderItem
{
public string Description { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TotalPrice => UnitPrice * Quantity;
}
これらの変更を反映するには、図 4 に示している SalesViewModel も変更しなければなりません。
図 4 SalesViewModel
public class SalesViewModel : ViewModelBase
{
private Order _selected;
public Order Selected
{
get => _selected;
set
{
Set(ref _selected, value);
}
}
public ObservableCollection<Order> Orders { get; private set; }
public async Task LoadDataAsync(MasterDetailsViewState viewState)
{
var orders = await DataService.GetOrdersAsync();
if (orders != null)
{
Orders = new ObservableCollection<Order>(orders);
RaisePropertyChanged("Orders");
}
if (viewState == MasterDetailsViewState.Both)
{
Selected = Orders.FirstOrDefault();
}
}
}
選択されたプロパティが変更されると、注文品が既に読み込まれているかどうかが確認され、読み込まれていない場合は、注文品を読み込むためにデータ サービスで GetOrderItemsAsync メソッドが呼び出されます。
このコードでは最後に SampleDataService クラスに変更を加えています。サンプル データを削除し、SQL Server アクセスを作成するためです (図 5 参照)。これがもうサンプルでないことを反映するために、このクラスの名前を DataService に変更しています。
図 5 データベースから注文を取得するコード
public static async Task<IEnumerable<Order>> GetOrdersAsync()
{
using (SqlConnection conn = new SqlConnection(
"Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
{
try
{
await conn.OpenAsync();
SqlCommand cmd = new SqlCommand("select o.OrderId, " +
"c.CustomerName, o.OrderDate, o.PickingCompletedWhen, " +
"sum(l.Quantity * l.UnitPrice) as OrderTotal " +
"from Sales.Orders o " +
"inner join Sales.Customers c on c.CustomerID = o.CustomerID " +
"inner join Sales.OrderLines l on o.OrderID = l.OrderID " +
"group by o.OrderId, c.CustomerName, o.OrderDate,
o.PickingCompletedWhen " +
"order by o.OrderDate desc", conn);
var results = new List<Order>();
using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
{
while(reader.Read())
{
var order = new Order
{
Company = reader.GetString(1),
OrderId = reader.GetInt32(0),
OrderDate = reader.GetDateTime(2),
OrderTotal = reader.GetDecimal(4),
DatePicked = !reader.IsDBNull(3) ? reader.GetDateTime(3) :
(DateTime?)null
};
results.Add(order);
}
return results;
}
}
catch
{
return null;
}
}
}
public static async Task<IEnumerable<OrderItem>> GetOrderItemsAsync(
int orderId)
{
using (SqlConnection conn = new SqlConnection(
"Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
{
try
{
await conn.OpenAsync();
SqlCommand cmd = new SqlCommand(
"select Description,Quantity,UnitPrice " +
$"from Sales.OrderLines where OrderID = {orderId}", conn);
var results = new List<OrderItem>();
using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
{
while (reader.Read())
{
var orderItem = new OrderItem
{
Description = reader.GetString(0),
Quantity = reader.GetInt32(1),
UnitPrice = reader.GetDecimal(2),
};
results.Add(orderItem);
}
return results;
}
}
catch
{
return null;
}
}
}
このコードは、.NET アプリケーションで使用されているものと同じです。つまり、いっさい変更は加えていません。ここで、変更を反映するために、SalesPage.xaml でリスト アイテム データ テンプレートを変更する必要があります (図 6 参照)。
図 6 リスト アイテム データ テンプレート
<DataTemplate x:Key="ItemTemplate" x:DataType="model:Order">
<Grid Height="64" Padding="0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Margin="12,0,0,0" VerticalAlignment="Center">
<TextBlock Text="{x:Bind Company}" Style="{ThemeResource ListTitleStyle}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind OrderDate.ToShortDateString()}"
Margin="0,0,12,0" />
<TextBlock Text="{x:Bind OrderTotal}" Margin="0,0,12,0" />
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate><DataTemplate x:Key="DetailsTemplate">
<views:SalesDetailControl MasterMenuItem="{Binding}"/>
</DataTemplate>
新しい Order クラスを参照するように DataType を変更し、TextBlocks に提供されるフィールドを変更する必要があります。また、注文の変更時に選択された注文品が読み込まれるように、SalesDetailControl.xaml.cs の分離コードも変更しなければなりません。これは OnMasterMenuItemChanged メソッドで行います。このメソッドを async メソッドに変換します。
private static async void OnMasterMenuItemChangedAsync(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var newOrder = e.NewValue as Order;
if (newOrder != null && newOrder.OrderItems == null)
newOrder.OrderItems = await
DataService.GetOrderItemsAsync((int)newOrder.OrderId);
}
次に、新しいフィールドを参照および表示するように、SalesDetailControl を変更する必要があります (図 7 参照)。
図 7 SalesDetailControl の変更
<Grid Name="block" Padding="0,15,0,0">
<Grid.Resources>
<Style x:Key="RightAlignField" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,12,0" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Margin="12,0,0,0"
Text="{x:Bind MasterMenuItem.Company, Mode=OneWay}"
Style="{StaticResource SubheaderTextBlockStyle}" />
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="12,12,0,12">
<TextBlock Text="Order date:"
Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,12,0"/>
<TextBlock Text="{x:Bind MasterMenuItem.OrderDate.ToShortDateString(),
Mode=OneWay}"
Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,12,0"/>
<TextBlock Text="Order total:" Style="{StaticResource BodyTextBlockStyle}"
Margin="0,0,12,0"/>
<TextBlock Text="{x:Bind MasterMenuItem.OrderTotal, Mode=OneWay}"
Style="{StaticResource BodyTextBlockStyle}" />
</StackPanel>
<grid:RadDataGrid ItemsSource="{x:Bind MasterMenuItem.OrderItems,
Mode=OneWay}" Grid.Row="2"
UserGroupMode="Disabled" Margin="12,0"
UserFilterMode="Disabled" BorderBrush="Transparent"
AutoGenerateColumns="False">
<grid:RadDataGrid.Columns>
<grid:DataGridTextColumn PropertyName="Description" />
<grid:DataGridNumericalColumn
PropertyName="UnitPrice" Header="Unit Price"
CellContentStyle="{StaticResource RightAlignField}"/>
<grid:DataGridNumericalColumn
PropertyName="Quantity" Header="Quantity"
CellContentStyle="{StaticResource RightAlignField}"/>
<grid:DataGridNumericalColumn
PropertyName="TotalPrice" Header="Total Price"
CellContentStyle="{StaticResource RightAlignField}"/>
</grid:RadDataGrid.Columns>
</grid:RadDataGrid>
</Grid>
コントロールの先頭に売り上げデータを表示し、Telerik RadDataGrid を使用して注文品を表示します。ここで、アプリケーションを実行すると、2 つ目のページに注文が取得されます (図 8 参照)。
図 8 データベースの注文を表示するアプリケーション
まだ空のメイン ページがあります。これを使用して、すべての顧客のグリッド表示します。MainPage.xaml で DataGrid をコンテンツ グリッドに追加します。
<grid:RadDataGrid ItemsSource="{Binding Customers}"/>
Page タグに名前空間を追加する必要があります。ただし、その構文と正しい名前空間を憶えておく必要はありません。マウス カーソルを RadDataGrid の上に置き、Ctrl キーを押しながらドット (.) キーを押すだけで、ボックスが開き、追加する正しい名前空間が示されます。追加した行からわかるとおり、ItemsSource プロパティを Customers にバインドしています。このページの DataContext は MainViewModel のインスタンスです。そのため、MainViewModel.cs でこのプロパティを作成しなければなりません (図 9 参照)。
図 9 MainViewModel クラス
public class MainViewModel : ViewModelBase
{
public ObservableCollection<Customer> Customers { get; private set; }
public async Task LoadCustomersAsync()
{
var customers = await DataService.GetCustomersAsync();
if (customers != null)
{
Customers = new ObservableCollection<Customer>(customers);
RaisePropertyChanged("Customers");
}
}
public MainViewModel()
{
LoadCustomersAsync();
}
}
ViewModel が作成されるときに顧客を読み込みます。ここで 1 つ注目する点は、読み込みが完了するまで待機していない点です。データが完全に読み込まれると、Customers プロパティに変更が加えられ、ビューにそのデータが読み込まれていることが ViewModel によって示されます。DataService の GetCustomersAsync メソッドは GetOrdersAsync によく似ています (図 10 参照)。
図 10 GetCustomersAsync メソッド
public static async Task<IEnumerable<Customer>> GetCustomersAsync()
{
using (SqlConnection conn = new SqlConnection(
"Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
{
try
{
await conn.OpenAsync();
SqlCommand cmd = new SqlCommand("select c.CustomerID,
c.CustomerName, " +
"cat.CustomerCategoryName, c.DeliveryAddressLine2,
c.DeliveryPostalCode, " +
"city.CityName, c.PhoneNumber " +
"from Sales.Customers c " +
"inner join Sales.CustomerCategories cat on c.CustomerCategoryID =
cat.CustomerCategoryID " +
"inner join Application.Cities city on c.DeliveryCityID =
city.CityID", conn);
var results = new List<Customer>();
using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
{
while (reader.Read())
{
var customer = new Customer
{
CustomerId = reader.GetInt32(0),
Name = reader.GetString(1),
Category = reader.GetString(2),
Address = reader.GetString(3),
PostalCode = reader.GetString(4),
City = reader.GetString(5),
Phone = reader.GetString(6)
};
results.Add(customer);
}
return results;
}
}
catch
{
return null;
}
}
}
これにより、アプリケーションを実行して、メイン ページに顧客を表示できます (図 11 参照)。Telerik グリッドを使用すると、多数の優れた機能を無料で取得できます。つまり、グループ化、並べ替え、およびフィルタリングがグリッドに組み込まれます。しかも、追加作業は発生しません。
図 11 グループ化とフィルタリングを実行した顧客グリッド
最後のしあげ
これで、顧客グリッドと、注文を表示するマスター/詳細ビューを備えた基本的な LOB アプリケーションができました。ただし、最後のしあげをいくつか使えます。側面バーにある両ページ用のアイコンが同じになっていますが、これらをカスタマイズできます。これらのアイコンは ShellViewModel で設定されます。ShellViewModel に移動すると、次のコメントが表示されます。このコメントには、各アイテムのアイコンやテキストを変更する場合の確認先が示されています。
// TODO WTS: Change the symbols for each item as appropriate for your app
// More on Segoe UI Symbol icons:
// https://docs.microsoft.com/windows/uwp/style/segoe-ui-symbol-font
// Or to use an IconElement instead of a Symbol see
// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/
projectTypes/navigationpane.md
// Edit String/en-US/Resources.resw: Add a menu item title for each page
IconElements を使用すると、(実際のコードのように) 他のソースのフォント シンボル アイコンやイメージを使用できます (他のフォントの .png ファイル、XAML パス、または文字を使用できます。詳細については、bit.ly/2zICuB2 (英語) を参照してください)。 ここでは Segoe UI Symbol の 2 つのシンボル People および ShoppingCart を使用することにします。そのために、コードで NavigationItems を次のように変更する必要があります。
_primaryItems.Add(new ShellNavigationItem("Shell_Main".GetLocalized(),
Symbol.People, typeof(MainViewModel).FullName));
_primaryItems.Add(new ShellNavigationItem("Shell_Sales".GetLocalized(),
(Symbol)0xE7BF, typeof(SalesViewModel).FullName));
最初のアイテムには、Symbol 列挙型内に既にシンボル (Symbol.People) がありますが、2 つ目にはそのような列挙型がないため、16 進数値を使用して Symbol 列挙型にキャストします。ページのタイトルとメニュー項目のキャプションを変更するために、Resources.resw を編集し、Shell_Main と Main_Title.Text を Customers に変更しました。また、一部のプロパティを変更することで、グリッドにカスタマイズもいくつか加えました。
<grid:RadDataGrid ItemsSource="{Binding Customers}" UserColumnReorderMode="Interactive"
ColumnResizeHandleDisplayMode="Always"
AlternationStep="2" AlternateRowBackground="LightBlue"/>
ライブ タイルをアプリケーションに追加する
ライブ タイルを追加してアプリケーションを強化することもできます。そのためには、ソリューション エクスプローラーに移動し、プロジェクト ノードを右クリックして、[Windows Template Studio]、[New feature] (新機能)、[Live Tile] (ライブ タイル) の順に選択します。[Next] (次へ) をクリックすると、影響のあるタイル (新しいタイルと変更済みタイルの両方) が表示されます。そのため、操作の結果が本当に気に入ったものになるかどうかを確認できます。[Finish] (完了) ボタンをクリックすると、アプリケーションに変更が加えられます。
ライブ タイルに追加するものはすべて用意されるため、必要なのはタイルのコンテンツを作成することのみです。この作業は LiveTileService.Samples では既に完了しています。これはメソッド SampleUpdate を LiveTileService に追加する部分クラスです。図 12 に示すように、このファイルの名前を「LiveTileService.LobData」に変更し、これに 2 つのメソッド UpdateCustomerCount および UpdateOrdersCount を追加します。これらのメソッドはデータベースに含まれている顧客数と注文数をライブ タイルに表示します。
図 12 ライブ タイルを更新するクラス
internal partial class LiveTileService
{
private const string TileTitle = "LoB App with UWP";
public void UpdateCustomerCount(int custCount)
{
string tileContent =
$@"There are {(custCount > 0 ? custCount.ToString() : "no")}
customers in the database";
UpdateTileData(tileContent, "Customer");
}
public void UpdateOrderCount(int orderCount)
{
string tileContent =
$@"There are {(orderCount > 0 ? orderCount.ToString() : "no")}
orders in the database";
UpdateTileData(tileContent,"Order");
}
private void UpdateTileData(string tileBody, string tileTag)
{
TileContent tileContent = new TileContent()
{
Visual = new TileVisual()
{
TileMedium = new TileBinding()
{
Content = new TileBindingContentAdaptive()
{
Children =
{
new AdaptiveText()
{
Text = TileTitle,
HintWrap = true
},
new AdaptiveText()
{
Text = tileBody,
HintStyle = AdaptiveTextStyle.CaptionSubtle,
HintWrap = true
}
}
}
},
TileWide = new TileBinding()
{
Content = new TileBindingContentAdaptive()
{
Children =
{
new AdaptiveText()
{
Text = $"{TileTitle}",
HintStyle = AdaptiveTextStyle.Caption
},
new AdaptiveText()
{
Text = tileBody,
HintStyle = AdaptiveTextStyle.CaptionSubtle,
HintWrap = true
}
}
}
}
}
};
var notification = new TileNotification(tileContent.GetXml())
{
Tag = tileTag
};
UpdateTile(notification);
}
}
初期化においては、つまり ActivationService の StartupAsync メソッドでは、もともと UpdateSample が呼び出されていました。これを新しい UpdateCustomerCount に置き換えます。
private async Task StartupAsync()
{
Singleton<LiveTileService>.Instance.UpdateCustomerCount(0);
ThemeSelectorService.SetRequestedTheme();
await Task.CompletedTask;
}
この時点では更新する顧客数はまだありません。更新は、MainViewModel で顧客を取得すると行われます。
public async Task LoadCustomersAsync()
{
var customers = await DataService.GetCustomersAsync();
if (customers != null)
{
Customers = new ObservableCollection<Customer>(customers);
RaisePropertyChanged("Customers");
Singleton<LiveTileService>.Instance.UpdateCustomerCount(Customers.Count);
}
}
注文数は、SalesViewModel で注文を取得すると更新されます。
public async Task LoadDataAsync(MasterDetailsViewState viewState)
{
var orders = await DataService.GetOrdersAsync();
if (orders != null)
{
Orders = new ObservableCollection<Order>(orders);
RaisePropertyChanged("Orders");
Singleton<LiveTileService>.Instance.UpdateOrderCount(Orders.Count);
}
if (viewState == MasterDetailsViewState.Both)
{
Selected = Orders.FirstOrDefault();
}
}
それにより、アプリケーションは図 13 のようになります。
図 13 完成したアプリケーション
このアプリケーションは、ローカル データベースから取得した顧客と注文を表示でき、顧客数と注文数でライブ タイルを更新します。一覧表示されている顧客をグループ化、並べ替え、およびフィルタリングすることや、マスター/詳細ビューを使用して注文を表示することができます。悪くありません。
まとめ
ご覧のとおり、UWP は小規模なアプリケーション専用ではありません。UWP を LOB アプリケーションに使用して、ローカル SQL Server を含む多くのソースからデータを取得できます (また、Entity Framework を ORM として使用することさえできます)。.NET Standard 2.0 を使用すると、.NET Framework で既に利用できる多くの API に対し、変更不要でアクセスできます。WTS を使えばすばやく作業を開始することができます。これにより、お気に入りのツールとベスト プラクティスを使用して、アプリケーションを迅速かつ簡単に作成することや、そのアプリケーションに Windows 機能を追加することが容易になります。アプリケーションの外観を向上するすばらしい UI コントロールがあり、スマートフォン、デスクトップ、Surface Hub、さらには HoloLens など、多様なデバイス上で、変更を加えることなくアプリケーションが実行されるようになります。
配置に関しては、アプリケーションを Store に送ることで、全世界のユーザーが見つけられるようになり、インストールおよびアンインストールすることや自動更新が簡単になります。Store 経由で配置することを望まない場合は、Web を使用して配置できます (bit.ly/2zH0nZY、英語)。ご覧のように、UWP はこのようなアプリケーションに必要なものすべてを提供するだけにとどまりません。積極的に開発されており、この先何年間にもわたって多くの機能強化が加えられていくという、大きなメリットも備わっています。そのため、Windows 用に新しい LOB アプリケーションを作成しなければならない場合は必ず UWP を検討する必要があります。
Bruno Sonnino は、2007 年から Microsoft Most Valuable Professional (MVP) になっています。彼は開発者、コンサルタント兼執筆者で、Windows 開発に関する多くの書籍や記事を執筆しています。Twitter は、@bsonnino (英語) からフォローできます。または、blogs.msmvps.com/bsonnino (英語) で彼のブログをご覧いただけます。
この記事のレビューに協力してくれたマイクロソフト技術スタッフの Clint Rutkas に心より感謝いたします。