はじめに
WindowChromeとは
通常のWindowの画面では最上部には、タイトルバーや閉じるボタンが配置されているプログラマーが触れない領域である非クライアント領域と、
その下側のクライアント領域があります。
画面にコントロールを配置する時は、このクライアント領域に配置します。
上部の非クライアント領域にContentの記述を拡張するための仕組みがWindowChromeです。
このがWindowChromeを使うことによって、画面全体を自由にデザインすることが可能となります。
章 | タイトル | 内容 |
---|---|---|
1 | DashboardSampleの移植 | サイトで見つけたDashboardSampleのViewを移植 |
2 | CustomChromeLibrary.dll | WindowChrome作成で必要なdllを直接使ったプログラミング |
参考にしたサイト
ここでの解説は、下記のCODE PROJECTのサイトより「WpfCustomChromeLibrary」をダウンロードして使用しています。
「C# Free Code - Download WpfCustomChromeLibrary - Java2s」
サイトの下の方の「Download」の下のwpfcustomchromelib.zipをクリックするとダウンロード出来ます。
解凍すると、wpfcustomchromelibというフォルダが出来上がります。この中には、
BuildProcessTemplates
Libraries
SampleApps
ThirdParty
というフォルダがあります。
wpfcustomchromelibごと、Dドライブ直下にコピー(又は移動)します。
Visual Studioを起動して、「プロジェクトを開く...」で、
D:\WpfCustomChromeLib\SampleApps\CustomChromeSample\CustomChromeSample.sln
を選択します。画面は下記のようになります。
※下記のurlからは簡単にプロジェクトのダウンロードができます。
WPF Custom Chrome Library - CodeProject
WpfCustomChromeLib (クリックすると拡大)
このプロジェクトは使いません。単に参考に表示しただけで、使用するのは、解凍した
wpfcustomchromelibの中のLibrariesフォルダの中のCustomChromeLibraryフォルダと
ThirdPartyフォルダの中のWPF Shell Integration Library v2フォルダです。
新しくLivetプロジェクトの作成
とりあえず、D:ドライブ直下にLivetWPFApplicationChrome1という名前でプロジェクト(ソリューション を作成します。
Chrome関連フォルダのコピー
次に、作ったプロジェクトをエックスプローラーで開いて、この中に、CustomChromeLibraryフォルダとWPF Shell Integration Library v2フォルダをコピーします。
エックスプローラー (クリックすると拡大)
新しく作ったプロジェクトにプロジェクト(フォルダ)を追加
先ほど作ったプロジェクトを開いて、ソリューションエックスプローラーのソリューション名を選択して下記のようにショートカットメニューで2つのプロジェクトを取り込みます。
Visual Stusio ソリューションエックスプローラー (クリックすると拡大)
Chrome関連フォルダを追加したLivetプロジェクト
次に追加したプロジェクトを「参照設定」します。
クラスの作成
CaptionButtonRectToMarginConverter.csとう名前で、LivetWPFApplicationChrome1の直下にクラスを作成します。
ViewModel
using System; using System; using System.Windows; using System.Windows.Data; using LivetWPFApplicationChrome1.ViewModels; using LivetWPFApplicationChrome1.Views; namespace LivetWPFApplicationChrome1 { public class CaptionButtonRectToMarginConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var captionLocation = (Rect)value; return new Thickness(0, captionLocation.Top + 6, -captionLocation.Right, 0); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotSupportedException(); } } }
MainWindow.xamlの書き換え
WindowChromeに書き換えます。その時、クラスがWondowからccl:CustomChromeWindowに変更しています。
ZAMLは下記の通りです。
ClosingとCloseの処理は全て、CustomChromeLibraryがおこなうので、ここでのEventTriggerは不要なのでコメントにしています。
<ccl:CustomChromeWindow x:Class="LivetWPFApplicationChrome1.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:shell="http://schemas.microsoft.com/winfx/2006/xaml/presentation/shell" xmlns:ccl="clr-namespace:CustomChromeLibrary;assembly=CustomChromeLibrary" xmlns:local="clr-namespace:LivetWPFApplicationChrome1" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:l="http://schemas.livet-mvvm.net/2011/wpf" xmlns:core="clr-namespace:Microsoft.Expression.Interactivity. Core;assembly=Microsoft.Expression.Interactions" xmlns:v="clr-namespace:LivetWPFApplicationChrome1.Views" xmlns:vm="clr-namespace:LivetWPFApplicationChrome1.ViewModels" WindowStartupLocation="CenterScreen" Title="MainWindow" Height="714" Width="1035"> <shell:WindowChrome.WindowChrome> <shell:WindowChrome ResizeBorderThickness="6" CaptionHeight="43" CornerRadius="0,0,0,0" GlassFrameThickness="0"> </shell:WindowChrome> </shell:WindowChrome.WindowChrome> <Window.Resources> <ResourceDictionary> <local:CaptionButtonRectToMarginConverter x:Key="CaptionButtonMarginConverter"/> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/Resources/Styles2.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <Window.DataContext> <vm:MainWindowViewModel/> </Window.DataContext> <i:Interaction.Triggers> <!--WindowのContentRenderedイベントのタイミングでViewModelの Initializeメソッドが呼ばれます--> <i:EventTrigger EventName="ContentRendered"> <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/> </i:EventTrigger> <l:InteractionMessageTrigger MessageKey="MessageKey2" Messenger="{Binding Messenger}"> <l:TransitionInteractionMessageAction WindowType="{x:Type v:Window2}" Mode="Modal"/> </l:InteractionMessageTrigger> <!-- 下記がないと、タスクバーが1つにならない --> <i:EventTrigger EventName="Loaded"> <i:InvokeCommandAction Command="{Binding Path=Loaded}" CommandParameter="{Binding Mode=OneTime, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/> </i:EventTrigger> <!-- <i:EventTrigger EventName="Closing"> <i:InvokeCommandAction Command="{Binding Path=Closing}" CommandParameter="{Binding Mode=OneTime,RelativeSource= {RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"/> </i:EventTrigger> <l:InteractionMessageTrigger MessageKey="Close" Messenger="{Binding Messenger}"> <l:WindowInteractionMessageAction/> </l:InteractionMessageTrigger> --> </i:Interaction.Triggers> <!-- Grid-1 --> <Grid> <!-- WindowChrome Start --> <Border Grid.RowSpan="2" BorderThickness="3" BorderBrush="Black"> <Border.Background> <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Color="Black" Offset="0" /> <GradientStop Color="Black" Offset="1" /> </LinearGradientBrush> </Border.Background> </Border> <Border BorderThickness="3,3,3,1" BorderBrush="Black" Margin="{Binding Path=CaptionButtonMargin}"> <Border.Background> <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Color="Black" Offset="0" /> <GradientStop Color="Black" Offset="1" /> </LinearGradientBrush> </Border.Background> <!--Window Icon and Title--> <StackPanel Orientation="Horizontal" Margin="0" VerticalAlignment="Top"> <TextBlock Text="WindowChrome" FontFamily="Calibri" FontWeight="Bold" FontSize="26" Foreground="Blue" /> </StackPanel> </Border> <ccl:CaptionButtons /> <!-- WindowChrome End --> <!-- Grid-2 --> <!--Content--> <Grid Grid.Row="1"> <!-- ★★★ --> <Grid> <TextBox Text="{Binding Path=txt本日}" TextAlignment="Center" Foreground="White" Background="Black" Height="17" HorizontalAlignment="Center" Name="txt本日" VerticalAlignment="Top" Width="81" Margin="809,3,123,0" /> <Button Command="{Binding Path=GotoCommand2}" Content="2.メニュー" FontSize="16" Height="35" Margin="39,0,712,569" Foreground="White" Style="{StaticResource CommandTemplate}" VerticalAlignment="Bottom" /> </Grid> <!-- ★★★ --> </Grid> </Grid> </ccl:CustomChromeWindow>
MainWindow.xamlのコードビハインドファイルの継承の変更
MainWindow.xamlのクラスをWondowからccl:CustomChromeWindowに変更したので、MainWindow.xamlのコード・ビハインドの継承をWondowからCustomChromeLibrary.CustomChromeWindowに変更します。
コードビハインドファイル
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Livet; using Livet.EventListeners.WeakEvents; namespace LivetWPFApplicationChrome1.Views { public partial class MainWindow : CustomChromeLibrary.CustomChromeWindow { public MainWindow() { InitializeComponent(); } } }
リソースフォルダの作成
名前は、Resources。LivetWPFApplicationChrome1を選択して下記のようにします。
リソースディクショナリの作成
名前は、Styles2.xamlで、Resourcesフォルダを選択して下記のようにします。
リソースディクショナリのZAMLは下記の通りです。
View<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="CommandTemplate" TargetType="{x:Type Button}"> <Setter Property="Background" Value="Green" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border Background="{TemplateBinding Background}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" /> </Border> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="DarkGoldenrod" /> </Trigger> </Style.Triggers> </Style> </ResourceDictionary>
ViewModelは下記の通りです。14,15,17,18,19,20行は自動作成に追加しています。
Closingの処理は全て、CustomChromeLibraryがおこなうので、ここでの記述は不要なので、コメントにしています。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using Livet; using Livet.Commands; using Livet.Messaging; using Livet.Messaging.IO; using Livet.EventListeners; using Livet.Messaging.Windows; //ICommand using System.Windows.Input; //Application //Window //MessageBox using System.Windows; using LivetWPFApplicationChrome1.Models; namespace LivetWPFApplicationChrome1.ViewModels { public class MainWindowViewModel : ViewModel { #region 変更通知プロパティ //----------------------------------------------- public string txt本日 { get; set; } public bool IsEnabled { get; set; } //----------------------------------------------- #endregion Window win = null; public MainWindowViewModel() { Loaded = new Livet.Commands.ListenerCommand<Window>((w) => { if (NeedHideOwner && w.Owner != null && w.Owner.Visibility == Visibility.Visible) { win = w; //w.Owner.Hide(); } }); /* Closing = new Livet.Commands.ListenerCommand<Window>((w) => { if (NeedHideOwner && w.Owner != null) { w.Owner.Show(); } }); */ //Initialize()では表示されない txt本日 = Convert.ToString(DateTime.Today.ToShortDateString()); } public bool NeedHideOwner { get; set; } public ICommand Loaded { get; private set; } // public ICommand Closing { get; private set; } public void Initialize() { if (win != null) win.Owner.Hide(); } #region GotoCommand2 //-------------------------------------------------------------- public ViewModelCommand GotoCommand2 { get { return new Livet.Commands.ViewModelCommand(Goto2); } } public void Goto2() { // Messenger.Raise(new TransitionMessage(new ViewModel2() // { NeedHideOwner = true }, "MessageKey2")); } //-------------------------------------------------------------- #endregion #region CloseCommand private ViewModelCommand _CloseCommand; public ViewModelCommand CloseCommand { get { if (_CloseCommand == null) { _CloseCommand = new ViewModelCommand(Close); } return _CloseCommand; } } public void Close() { var window = Application.Current.Windows.OfType<Window>(). SingleOrDefault((w) => w.IsActive); window.Close(); } #endregion } }
Close処理
CustomChromeLibraryの下記のクラスでClose処理をしています。
CloseButton.cs
実行
LivetWPFApplicationChrome1プロジェクトをビルドして、ソリューションをビルドして実行します。
2つ目の画面を下記の通り作ります。MainWindow.xamlのコード・ビハインドと同様に継承も修正します。
View<ccl:CustomChromeWindow x:Class="LivetWPFApplicationChrome1.Views.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:shell="http://schemas.microsoft.com/winfx/2006/xaml/presentation/shell" xmlns:ccl="clr-namespace:CustomChromeLibrary;assembly=CustomChromeLibrary" xmlns:local="clr-namespace:LivetWPFApplicationChrome1" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:l="http://schemas.livet-mvvm.net/2011/wpf" xmlns:core="clr-namespace:Microsoft.Expression.Interactivity. Core;assembly=Microsoft.Expression.Interactions" xmlns:v="clr-namespace:LivetWPFApplicationChrome1.Views" xmlns:vm="clr-namespace:LivetWPFApplicationChrome1.ViewModels" WindowStartupLocation="CenterScreen" Title="Window2" Height="714" Width="1035"> <shell:WindowChrome.WindowChrome> <shell:WindowChrome ResizeBorderThickness="6" CaptionHeight="43" CornerRadius="0,0,0,0" GlassFrameThickness="0"> </shell:WindowChrome> </shell:WindowChrome.WindowChrome> <Window.Resources> <ResourceDictionary> <local:CaptionButtonRectToMarginConverter x:Key= "CaptionButtonMarginConverter"/> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/Resources/Styles2.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <i:Interaction.Triggers> <!--WindowのContentRenderedイベントのタイミングでViewModelの Initializeメソッドが呼ばれます--> <i:EventTrigger EventName="ContentRendered"> <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/> </i:EventTrigger> <!-- 下記がないと、タスクバーが1つにならない --> <i:EventTrigger EventName="Loaded"> <i:InvokeCommandAction Command="{Binding Path=Loaded}" CommandParameter="{Binding Mode=OneTime,RelativeSource= {RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"/> </i:EventTrigger> <!-- <i:EventTrigger EventName="Closing"> <i:InvokeCommandAction Command="{Binding Path=Closing}" CommandParameter="{Binding Mode=OneTime,RelativeSource= {RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"/> </i:EventTrigger> <l:InteractionMessageTrigger MessageKey="Close" Messenger= "{Binding Messenger}"> <l:WindowInteractionMessageAction/> </l:InteractionMessageTrigger> --> </i:Interaction.Triggers> <Window.DataContext> <vm:ViewModel2/> </Window.DataContext> <!-- Grid-1 --> <Grid> <!-- WindowChrome Start --> <Border Grid.RowSpan="2" BorderThickness="3" BorderBrush="Black"> <Border.Background> <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Color="Black" Offset="0" /> <GradientStop Color="Black" Offset="1" /> </LinearGradientBrush> </Border.Background> </Border> <!--title bar--> <Border BorderThickness="3,3,3,1" BorderBrush="Black" Margin= "{Binding Path=CaptionButtonMargin}"> <Border.Background> <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Color="Black" Offset="0" /> <GradientStop Color="Black" Offset="1" /> </LinearGradientBrush> </Border.Background> <StackPanel Orientation="Horizontal" Margin="0" VerticalAlignment="Top"> <TextBlock Text="WindowChrome" FontFamily="Calibri" FontWeight= "Bold" FontSize="26" Foreground="Blue" /> </StackPanel> </Border> <ccl:CaptionButtons /> <!-- WindowChrome End --> <!-- Grid-2 --> <!--Content--> <Grid Grid.Row="1"> <!-- ★★★ --> <Grid> <TextBox Text="{Binding Path=txt本日}" TextAlignment="Center" Foreground="White" Background="Black" Height="17" HorizontalAlignment="Center" Name="txt本日" VerticalAlignment="Top" Width="81" Margin="809,3,123,0" /> </Grid> <!-- ★★★ --> </Grid> </Grid> </ccl:CustomChromeWindow>
画面は下記のようになります。
Window2.zaml
ViewModelは下記の通りです。
ViewModel
using System; // ObservableCollection using System.Collections.Generic; using System.Linq; //INotifyPropertyChanged //PropertyChanged using System.ComponentModel; //参照設定が必要 //using System.Configuration; using Livet; using Livet.Commands; using Livet.Messaging; //CloseCommand using Livet.Messaging.Windows; //ICommand using System.Windows.Input; //MessageBox using System.Windows; namespace LivetWPFApplicationChrome1.ViewModels { public class ViewModel2 : ViewModel { #region 変更通知プロパティ //----------------------------------------------- public string txtMessage { get; set; } public string txt本日 { get; set; } public bool IsEnabled { get; set; } //----------------------------------------------- #endregion Window win = null; public ViewModel2() { Loaded = new Livet.Commands.ListenerCommand<Window>((w) => { if (NeedHideOwner && w.Owner != null && w.Owner.Visibility == Visibility.Visible) { win = w; //w.Owner.Hide(); } }); /* Closing = new Livet.Commands.ListenerCommand<Window>((w) => { if (NeedHideOwner && w.Owner != null) { w.Owner.Show(); } }); */ //Initialize()では表示されない txt本日 = Convert.ToString(DateTime.Today.ToShortDateString()); } public bool NeedHideOwner { get; set; } public ICommand Loaded { get; private set; } // public ICommand Closing { get; private set; } public void Initialize() { if (win != null) win.Owner.Hide(); } #region GotoCommand //--------------------------------------------------------------- public ViewModelCommand GotoCommand3 { get { return new Livet.Commands.ViewModelCommand(Goto3); } } public void Goto3() { Messenger.Raise(new TransitionMessage(new ViewModel3() { NeedHideOwner = true }, "MessageKey3")); } //---------------------------------------------------------------- #endregion #region CloseCommand private ViewModelCommand _CloseCommand; public ViewModelCommand CloseCommand { get { if (_CloseCommand == null) { _CloseCommand = new ViewModelCommand(Close); } return _CloseCommand; } } public void Close() { var window = Application.Current.Windows.OfType<Window>().SingleOrDefault((w) => w.IsActive); window.Close(); } #endregion } }
はじめに