はじめに
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
}
}
はじめに










