Introduction
This is the fourth article in the WPFSpark series and it was long due! For the past few months,
I was busy creating a few more controls for WPFSpark and I am glad to say that the
WPFSpark project has finally hit v1.0. If you are reading this article first
in the WPFSpark series, WPFSpark
is an Open Source project in CodePlex containing a library of user controls providing rich user experience. Till now I have covered three controls
in WPFSpark - SprocketControl
, ToggleSwitch
, and FluidWrapPanel
.
I have added four more controls to WPFSpark - SparkWindow
,
FluidPivotPanel
, FluidProgressBar
, and FluidStatusBar
. I will be describing
them in detail, one by one, in this article and the forthcoming articles.
The previous articles in the WPFSpark series can be accessed here:
- WPFSpark: 1 of n: SprocketControl
- WPFSpark: 2 of n: ToggleSwitch
- WPFSpark: 3 of n: FluidWrapPanel
In this article, I describe in detail the fourth control in this library - the SparkWindow
control. It derives from
System.Windows.Window
and provides a rich user experience.
Inspiration
I occasionally create small tools (using WPF) at work to enhance the productivity of my fellow team members (as well as myself). In most of these tools, I ensure that
the window has no border and covers the entire desktop area excluding the taskbar. Also, the window doesn't have a separate and distinct title bar of its own. The title
bar shares space with the client area. This is because I want the user's undivided attention while using the application. This led to the genesis of SparkWindow
allowing me to reuse my code across several applications.
A few months back, when the Windows 8 Developer Preview got released, I was happy to find that Microsoft is following a similar approach for their Metro Apps.
I also learnt a lot from the book WPF Control Development Unleashed
by Pavan Podila. It's a really great resource for people who want to delve deep into custom control development.
SparkWindow Demystified
WindowMode
Initially, I created the template for a full screen window. Then later I added the capability to have non-fullscreen fixed size windows.
WindowMode
defines whether SparkWindow
can exist as a full screen window (which I have termed as a Pane)
or as a non-fullscreen fixed size window. It also defines which system buttons will be available in the right side of the titlebar area. The WindowMode
enum defines five modes in which the SparkWindow
can be:
Pane
- Fullscreen window with Minimize and Close buttons.
PaneCanClose
- Fullscreen window with Minimize and Close buttons.
CanClose
- Non-fullscreen, fixed size window with Close button only.
CanMinimize
- Non-fullscreen, fixed size window with Minimize and Close buttons.
CanMaximize
- Non-fullscreen, fixed size window with Minimize, Maximize, and Close buttons.
public enum WindowMode
{
Pane = 0,
PaneCanClose = 1,
CanClose = 2,
CanMinimize = 3,
CanMaximize = 4
}
The default WindowMode
is WindowMode.Pane
.
One more system button, About, can be added by setting the IsAboutEnabled
property. The About
button displays
a Question Mark(?) symbol. I will speak more about it later.
The WPFSpark demo application uses a SparkWindow
as its main window with the WindowFrameMode
property set to CanMinimize
.
SparkWindow
SparkWindow
is a custom control which derives from Systems.Windows.Window
. By default, the SparkWindow
is a lookless control.
Lookless controls provide a default template for the control and allow the user to replace the template with their own template without disturbing the control template.
There are a few basic steps that must be followed to create a lookless control. Here is what I did to make SparkWindow
lookless:
- Add the SparkWindow class
First, I added a C# class called SparkWindow
to the project. This would contain the control logic. I will be adding the logic later.
- Add the Themes\Generic.xaml ResourceDictionary
Next, I added a folder called Themes to the project and within it, I added a new ResourceDictionary
called Generic.xaml.
In this resource dictionary, I added the default styles of SparkWindow
and the various system buttons - About, Minimize, Maximize, and Close.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfspark="clr-namespace:WPFSpark">
-->
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly=wpfspark:SparkWindow,
ResourceId=SparkMinimizeButtonTemplate}"
TargetType="{x:Type Button}">
<Viewbox HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Canvas Height="16"
Width="16">
<Rectangle x:Name="BorderRect"
Width="15"
Height="15"
Stroke="Transparent"
Fill="Transparent"
StrokeThickness="0.5"></Rectangle>
<Line X1="3"
Y1="12"
X2="12"
Y2="12"
Stroke="White"
StrokeThickness="0.5"></Line>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver"
Value="true">
<Setter TargetName="BorderRect"
Property="Stroke"
Value="Gray"></Setter>
<Setter TargetName="BorderRect"
Property="RadiusX"
Value="0"></Setter>
<Setter TargetName="BorderRect"
Property="RadiusY"
Value="0"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99FFFFFF"
Offset="0"></GradientStop>
<GradientStop Color="Black"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="BorderRect"
Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="5"></OuterGlowBitmapEffect>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed"
Value="true">
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99333333"
Offset="0"></GradientStop>
<GradientStop Color="#99FFFFFF"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkMinimizeButton}"
TargetType="{x:Type Button}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="ToolTip"
Value="Minimize"></Setter>
<Setter Property="Template"
Value="{StaticResource {ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkMinimizeButtonTemplate}}" />
</Style>
-->
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkMaximizeButtonTemplate}"
TargetType="{x:Type Button}">
<Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas Height="16"
Width="16">
<Rectangle x:Name="BorderRect"
Width="15"
Height="15"
Stroke="Transparent"
Fill="Transparent"
StrokeThickness="0.5"></Rectangle>
<Rectangle Canvas.Left="2.5"
Canvas.Top="3"
Stroke="White"
StrokeThickness="0.5"
Fill="Transparent"
Width="10"
Height="10"></Rectangle>
<Rectangle Canvas.Left="2.5"
Canvas.Top="3"
Stroke="White"
StrokeThickness="0.5"
Fill="White"
Width="10"
Height="2"></Rectangle>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver"
Value="true">
<Setter TargetName="BorderRect"
Property="Stroke"
Value="Gray"></Setter>
<Setter TargetName="BorderRect"
Property="RadiusX"
Value="0"></Setter>
<Setter TargetName="BorderRect"
Property="RadiusY"
Value="0"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99FFFFFF"
Offset="0"></GradientStop>
<GradientStop Color="Black"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="BorderRect"
Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="5"></OuterGlowBitmapEffect>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed"
Value="true">
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99333333"
Offset="0"></GradientStop>
<GradientStop Color="#99FFFFFF"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkMaximizeButton}"
TargetType="{x:Type Button}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="ToolTip"
Value="Maximize"></Setter>
<Setter Property="Template"
Value="{StaticResource {ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkMaximizeButtonTemplate}}" />
</Style>
-->
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkCloseButtonTemplate}"
TargetType="{x:Type Button}">
<Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas Width="16"
Height="16">
<Rectangle x:Name="BorderRect"
Width="15"
Height="15"
Stroke="Transparent"
Fill="Transparent"
StrokeThickness="0.5">
<Rectangle.BitmapEffect>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="0"></OuterGlowBitmapEffect>
</Rectangle.BitmapEffect>
</Rectangle>
<Line X1="3"
Y1="3"
X2="12"
Y2="12"
Stroke="White"
StrokeThickness="0.5"></Line>
<Line X1="12"
Y1="3"
X2="3"
Y2="12"
Stroke="White"
StrokeThickness="0.5"></Line>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver"
Value="true">
<Setter TargetName="BorderRect"
Property="Stroke"
Value="Gray"></Setter>
<Setter TargetName="BorderRect"
Property="RadiusX"
Value="0"></Setter>
<Setter TargetName="BorderRect"
Property="RadiusY"
Value="0"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99FFFFFF"
Offset="0"></GradientStop>
<GradientStop Color="Black"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="BorderRect"
Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="5"></OuterGlowBitmapEffect>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed"
Value="true">
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99333333"
Offset="0"></GradientStop>
<GradientStop Color="#99FFFFFF"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkCloseButton}"
TargetType="{x:Type Button}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="ToolTip"
Value="Close"></Setter>
<Setter Property="Template"
Value="{StaticResource {ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkCloseButtonTemplate}}" />
</Style>
-->
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkAboutButtonTemplate}"
TargetType="{x:Type Button}">
<Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas Width="16"
Height="16">
<Rectangle x:Name="BorderRect"
Width="15"
Height="15"
Stroke="Transparent"
Fill="Transparent"
Margin="0"
StrokeThickness="0.5">
<Rectangle.BitmapEffect>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="0"></OuterGlowBitmapEffect>
</Rectangle.BitmapEffect>
</Rectangle>
-->
<Path Stroke="LightGray"
StrokeThickness="0"
VerticalAlignment="Top"
HorizontalAlignment="Center"
Fill="LightGray"
Margin="4.5,-3,0,0"
StrokeLineJoin="Round"
Data="F1M2.81640625,14.1291275024414L2.87499237060547,14.1315307617188
2.9311215877533,14.1387405395508 2.98479437828064,14.1507568359375
3.03601050376892,14.1675796508789 3.08476996421814,14.189208984375
3.1310727596283,14.2156448364258 3.17491912841797,14.2468872070313
3.21630859375,14.2829360961914 3.25395965576172,14.3227233886719
3.28659057617188,14.3651809692383 3.31420135498047,14.4103088378906
3.3367919921875,14.4581069946289 3.35436248779297,14.5085754394531
3.36691284179688,14.5617141723633 3.37444305419922,14.6175231933594
3.376953125,14.6760025024414 3.37444305419922,14.7329864501953
3.36691284179688,14.787727355957 3.35436248779297,14.8402252197266
3.3367919921875,14.8904800415039 3.31420135498047,14.9384918212891
3.28659057617188,14.984260559082 3.25395965576172,15.0277862548828
3.21630859375,15.0690689086914 3.17491912841797,15.1067199707031
3.1310727596283,15.1393508911133 3.08476996421814,15.1669616699219
3.03601050376892,15.1895523071289 2.98479437828064,15.2071228027344
2.9311215877533,15.2196731567383 2.87499237060547,15.2272033691406
2.81640625,15.2297134399414 2.75952911376953,15.2272033691406
2.70510864257813,15.2196731567383 2.65314483642578,15.2071228027344
2.6036376953125,15.1895523071289 2.55658721923828,15.1669616699219
2.51199340820313,15.1393508911133 2.46985626220703,15.1067199707031
2.43017578125,15.0690689086914 2.39412689208984,15.0277862548828
2.36288452148438,14.984260559082 2.33644866943359,14.9384918212891
2.3148193359375,14.8904800415039 2.29799652099609,14.8402252197266
2.28598022460938,14.787727355957 2.27877044677734,14.7329864501953
2.2763671875,14.6760025024414 2.27877044677734,14.6175231933594
2.28598022460938,14.5617141723633 2.29799652099609,14.5085754394531
2.3148193359375,14.4581069946289 2.33644866943359,14.4103088378906
2.36288452148438,14.3651809692383 2.39412689208984,14.3227233886719
2.43017578125,14.2829360961914 2.46985626220703,14.2468872070313
2.51199340820313,14.2156448364258 2.55658721923828,14.189208984375
2.6036376953125,14.1675796508789 2.65314483642578,14.1507568359375
2.70510864257813,14.1387405395508 2.75952911376953,14.1315307617188
2.81640625,14.1291275024414z M3.0283203125,5.13986968994141L3.15366363525391,
5.14211273193359 3.27676391601563,5.14884185791016 3.39762115478516,
5.16005706787109 3.5162353515625,5.17575836181641 3.63260650634766,
5.19594573974609 3.74673461914063,5.22061920166016
3.85861968994141,5.24977874755859 3.96826171875,5.28342437744141
4.07496643066406,5.32139587402344 4.17803955078125,5.36353302001953
4.27748107910156,5.40983581542969 4.373291015625,5.46030426025391
4.46546936035156,5.51493835449219 4.55401611328125,5.57373809814453
4.63893127441406,5.63670349121094 4.72021484375,5.70383453369141
4.79754638671875,5.77497100830078 4.87060546875,5.84995269775391
4.93939208984375,5.92877960205078 5.00390625,6.01145172119141
5.06414794921875,6.09796905517578 5.1201171875,6.18833160400391
5.17181396484375,6.28253936767578 5.21923828125,6.38059234619141
5.26169586181641,6.48233032226563 5.29849243164063,6.58759307861328
5.32962799072266,6.69638061523438 5.3551025390625,6.80869293212891
5.37491607666016,6.92453002929688 5.38906860351563,7.04389190673828
5.39756011962891,7.16677856445313 5.400390625,7.29319000244141
5.398681640625,7.38953399658203 5.3935546875,7.48374223709106
5.385009765625,7.57581377029419 5.373046875,7.66574907302856
5.357666015625,7.75354814529419 5.3388671875,7.83921051025391
5.316650390625,7.92273712158203 5.291015625,8.00412750244141
5.26265716552734,8.08370208740234 5.23226928710938,8.16178131103516
5.19985198974609,8.23836517333984 5.1654052734375,8.31345367431641
5.12892913818359,8.38704681396484 5.09042358398438,8.45914459228516
5.04988861083984,8.52974700927734 5.00732421875,8.59885406494141
4.96294403076172,8.66667938232422 4.91696166992188,8.73343658447266
4.86937713623047,8.79912567138672 4.8201904296875,8.86374664306641
4.76940155029297,8.92729949951172 4.71701049804688,8.98978424072266
4.66301727294922,9.05120086669922 4.607421875,9.11154937744141
4.49420166015625,9.23032379150391 4.380126953125,9.34738922119141
4.26519775390625,9.46274566650391 4.1494140625,9.57639312744141
4.06092071533203,9.66018676757813 3.97659301757813,9.74066925048828
3.89643096923828,9.81784057617188 3.8204345703125,9.89170074462891
3.74860382080078,9.96224975585938 3.68093872070313,10.0294876098633
3.61743927001953,10.0934143066406 3.55810546875,10.1540298461914
3.50240325927734,10.2122955322266 3.44979858398438,10.269172668457
3.40029144287109,10.3246612548828 3.3538818359375,10.3787612915039
3.31056976318359,10.4314727783203 3.27035522460938,10.482795715332
3.23323822021484,10.5327301025391 3.19921875,10.5812759399414
3.16797637939453,10.6289672851563 3.13919067382813,10.6763381958008
3.11286163330078,10.723388671875 3.0889892578125,10.7701187133789
3.06757354736328,10.8165283203125 3.04861450195313,10.8626174926758
3.03211212158203,10.9083862304688 3.01806640625,10.9538345336914
3.00605010986328,11.0001373291016 2.99563598632813,11.048469543457
2.98682403564453,11.0988311767578 2.9796142578125,11.1512222290039
2.97400665283203,11.2056427001953 2.97000122070313,11.262092590332
2.96759796142578,11.3205718994141 2.966796875,11.3810806274414
2.9676513671875,11.4587860107422 2.97021484375,11.5346755981445
2.9744873046875,11.6087493896484 2.98046875,11.6810073852539
2.9881591796875,11.7514495849609 2.99755859375,11.8200759887695
3.0086669921875,11.8868865966797 3.021484375,11.9518814086914
3.03515625,12.0147399902344 3.04882788658142,12.0751419067383
3.06249976158142,12.1330871582031 3.07617163658142,12.1885757446289
3.08984351158142,12.2416076660156 3.103515625,12.2921829223633
3.1171875,12.3403015136719 3.130859375,12.3859634399414
2.5771484375,12.3859634399414 2.56032562255859,12.3415832519531
2.54403686523438,12.2938919067383 2.52828216552734,12.2428894042969
2.5130615234375,12.1885757446289 2.49837493896484,12.1309509277344
2.48422241210938,12.0700149536133 2.47060394287109,12.0057678222656
2.45751953125,11.9382095336914 2.44550323486328,11.8690490722656
2.43508911132813,11.7999954223633 2.42627716064453,11.7310485839844
2.4190673828125,11.6622085571289 2.41345977783203,11.5934753417969
2.40945434570313,11.5248489379883 2.40705108642578,11.4563293457031
2.40625,11.3879165649414 2.40758514404297,11.3085021972656
2.41159057617188,11.2309036254883 2.41826629638672,11.1551208496094
2.4276123046875,11.0811538696289 2.43962860107422,11.0090026855469
2.45431518554688,10.9386672973633 2.47167205810547,10.8701477050781
2.49169921875,10.8034439086914 2.51412963867188,10.7381286621094
2.5386962890625,10.6737747192383 2.56539916992188,10.6103820800781
2.59423828125,10.5479507446289 2.62521362304688,10.4864807128906
2.6583251953125,10.4259719848633 2.69357299804688,10.3664245605469
2.73095703125,10.3078384399414 2.77037048339844,10.2499465942383
2.81170654296875,10.1924819946289 2.85496520996094,10.1354446411133
2.900146484375,10.0788345336914 2.94725036621094,10.0226516723633
2.99627685546875,9.96689605712891 3.04722595214844,9.91156768798828
3.10009765625,9.85666656494141 3.21054077148438,9.74686431884766
3.3265380859375,9.63620758056641 3.44808959960938,9.52469635009766
3.5751953125,9.41233062744141 3.68414282798767,9.31214141845703
3.79223608970642,9.20981597900391 3.89947485923767,9.10535430908203
4.005859375,8.99875640869141 4.10882568359375,8.88916778564453
4.205810546875,8.77573394775391 4.29681396484375,8.65845489501953
4.3818359375,8.53733062744141 4.42183685302734,8.47511291503906
4.45980834960938,8.41150665283203 4.49575042724609,8.34651184082031
4.5296630859375,8.28012847900391 4.56154632568359,8.21235656738281
4.59140014648438,8.14319610595703 4.61922454833984,8.07264709472656
4.64501953125,8.00070953369141 4.66825103759766,7.92716979980469
4.68838500976563,7.85181427001953 4.70542144775391,7.77464294433594
4.7193603515625,7.69565582275391 4.73020172119141,7.61485290527344
4.73794555664063,7.53223419189453 4.74259185791016,7.44779968261719
4.744140625,7.36154937744141 4.74216461181641,7.26878356933594
4.73623657226563,7.17847490310669 4.72635650634766,7.09062242507935
4.7125244140625,7.00522613525391 4.69474029541016,6.92228698730469
4.67300415039063,6.84180450439453 4.64731597900391,6.76377868652344
4.61767578125,6.68820953369141 4.58451080322266,6.61525726318359
4.54824829101563,6.54508209228516 4.50888824462891,6.47768402099609
4.4664306640625,6.41306304931641 4.42087554931641,6.35121965408325
4.37222290039063,6.29215288162231 4.32047271728516,6.23586273193359
4.265625,6.18235015869141 4.20778656005859,6.13177490234375
4.14706420898438,6.08429718017578 4.08345794677734,6.0399169921875
4.0169677734375,5.99863433837891 3.94759368896484,5.96044921875
3.87533569335938,5.92536163330078 3.80019378662109,5.89337158203125
3.72216796875,5.86447906494141 3.64168548583984,5.83884429931641
3.5591733455658,5.81662797927856 3.47463202476501,5.79782915115356
3.38806128501892,5.78244781494141 3.29946112632751,5.77048492431641
3.2088315486908,5.76194000244141 3.11617279052734,5.75681304931641
3.021484375,5.75510406494141 2.94154930114746,5.75598526000977
2.86238861083984,5.75862884521484 2.78400230407715,5.76303482055664
2.70639038085938,5.76920318603516 2.62955284118652,5.77713394165039
2.55348968505859,5.78682708740234 2.47820091247559,5.79828262329102
2.4036865234375,5.81150054931641 2.32994651794434,5.82648086547852
2.25698089599609,5.84322357177734 2.18478965759277,5.86172866821289
2.11337280273438,5.88199615478516 2.0427303314209,5.90402603149414
1.97286224365234,5.92781829833984 1.90376853942871,5.95337295532227
1.83544921875,5.98069000244141 1.70059967041016,6.03964996337891
1.56777954101563,6.10373687744141 1.43698883056641,6.17295074462891
1.3082275390625,6.24729156494141 1.18149566650391,6.32675933837891
1.05679321289063,6.41135406494141 0.934120178222656,6.50107574462891
0.813476622104645,6.59592437744141 0.813476622104645,5.86447906494141
0.935615539550781,5.77518463134766 1.05935668945313,5.69187164306641
1.18470001220703,5.61454010009766 1.3116455078125,5.54319000244141
1.44019317626953,5.47782182693481 1.57034301757813,5.41843461990356
1.70209503173828,5.36502838134766 1.83544921875,5.31760406494141
1.97147369384766,5.27594757080078 2.11123657226563,5.23984527587891
2.25473785400391,5.20929718017578 2.4019775390625,5.18430328369141
2.55295562744141,5.16486358642578 2.70767211914063,5.15097808837891
2.86612701416016,5.14264678955078 3.0283203125,5.13986968994141z">
</Path>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver"
Value="true">
<Setter TargetName="BorderRect"
Property="Stroke"
Value="Gray"></Setter>
<Setter TargetName="BorderRect"
Property="RadiusX"
Value="0"></Setter>
<Setter TargetName="BorderRect"
Property="RadiusY"
Value="0"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99FFFFFF"
Offset="0"></GradientStop>
<GradientStop Color="Black"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="BorderRect"
Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="5"></OuterGlowBitmapEffect>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed"
Value="true">
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99333333"
Offset="0"></GradientStop>
<GradientStop Color="#99FFFFFF"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkAboutButton}"
TargetType="{x:Type Button}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="ToolTip"
Value="About"></Setter>
<Setter Property="Template"
Value="{StaticResource {ComponentResourceKey
TypeInTargetAssembly=wpfspark:SparkWindow,
ResourceId=SparkAboutButtonTemplate}}" />
</Style>
-->
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly=
wpfspark:SparkWindow, ResourceId=SparkWindowTemplate}"
TargetType="wpfspark:SparkWindow">
<Border Name="OuterFrame"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="{Binding Path=OuterBorderBrush, RelativeSource=
{RelativeSource FindAncestor,AncestorType={x:Type wpfspark:SparkWindow}}}"
BorderThickness="{Binding Path=OuterBorderThickness,
RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type wpfspark:SparkWindow}}}"
CornerRadius="{Binding Path=OuterBorderCornerRadius,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
SnapsToDevicePixels="True"
Background="Transparent">
<Border Name="windowFrame"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="{Binding Path=InnerBorderBrush,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
BorderThickness="{Binding Path=InnerBorderThickness,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
CornerRadius="{Binding Path=InnerBorderCornerRadius,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
SnapsToDevicePixels="True"
Background="{TemplateBinding Background}">
<Grid ShowGridLines="False">
-->
<Grid.RowDefinitions>
<RowDefinition Height="2"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="2"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="1"
Grid.RowSpan="3"
Grid.Column="1"
Margin="0,0,0,0">
<TextBlock Name="PART_TitleText"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"
Foreground="{TemplateBinding Foreground}"
FontStyle="{TemplateBinding FontStyle}"
VerticalAlignment="Top"
Margin="{Binding Path=TitleMargin, RelativeSource=
{RelativeSource FindAncestor,AncestorType=
{x:Type wpfspark:SparkWindow}}}"
Effect="{Binding Path=TitleEffect,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
Text="{Binding Path=Title, RelativeSource=
{RelativeSource FindAncestor,AncestorType=
{x:Type wpfspark:SparkWindow}},
NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.3"
Storyboard.TargetProperty="Margin"
From="400,-30,0,0"
To="0,-30,0,0">
<ThicknessAnimation.EasingFunction>
<ExponentialEase EasingMode="EaseOut"
Exponent="2"></ExponentialEase>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</Grid>
<Border Name="PART_TitleBar"
Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
Background="Black"
Opacity="0" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="5" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="1"
Grid.RowSpan="4"
Grid.Column="1"
Margin="0,0,0,0">
</Grid>
<Button Name="PART_About"
Margin="0,5,0,0"
Grid.Column="1"
Style="{StaticResource {ComponentResourceKey
TypeInTargetAssembly=wpfspark:SparkWindow,
ResourceId=SparkAboutButton}}">
</Button>
<Button Name="PART_Minimize"
Margin="0,5,0,0"
Grid.Column="2"
Style="{StaticResource {ComponentResourceKey
TypeInTargetAssembly=wpfspark:SparkWindow,
ResourceId=SparkMinimizeButton}}">
</Button>
<Button Name="PART_Maximize"
Margin="0,5,0,0"
Grid.Column="3"
Style="{StaticResource {ComponentResourceKey
TypeInTargetAssembly=wpfspark:SparkWindow,
ResourceId=SparkMaximizeButton}}">
</Button>
<Button Name="PART_Close"
Margin="0,5,0,0"
Grid.Column="4"
Style="{StaticResource {ComponentResourceKey
TypeInTargetAssembly=wpfspark:SparkWindow,
ResourceId=SparkCloseButton}}"></Button>
</Grid>
-->
<AdornerDecorator Grid.Row="3"
Grid.Column="0">
<ContentPresenter />
</AdornerDecorator>
</Grid>
</Border>
</Border>
</ControlTemplate>
<Style TargetType="{x:Type wpfspark:SparkWindow}">
<Setter Property="AllowsTransparency"
Value="False"></Setter>
<Setter Property="ResizeMode"
Value="NoResize"></Setter>
<Setter Property="MinHeight"
Value="100"></Setter>
<Setter Property="MinWidth"
Value="200"></Setter>
<Setter Property="MaxWidth"
Value="{DynamicResource {x:Static
SystemParameters.MaximizedPrimaryScreenWidthKey}}"></Setter>
<Setter Property="MaxHeight"
Value="{DynamicResource {x:Static
SystemParameters.MaximizedPrimaryScreenHeightKey}}"></Setter>
<Setter Property="WindowStyle"
Value="None"></Setter>
<Setter Property="Template"
Value="{StaticResource {ComponentResourceKey
TypeInTargetAssembly=wpfspark:SparkWindow,
ResourceId=SparkWindowTemplate}}" />
</Style>
</ResourceDictionary>
In the style, I have set AllowsTransparency
as False
in order to provide better performance. For more details on the impact
of the AllowsTransparency
property on the performance of a window, check out this blog
by Jeremy Alles.The MaxWidth
and MaxHeight
properties are bound to SystemParameters.MaximizedPrimaryScreenWidthKey
and SystemParameters.MaximizedPrimaryScreenHeightKey
, respectively so that when they are in maximized state, they do not overlap the taskbar.
If you notice, while setting the key (x:Key
) for resources, instead of providing a simple string, I have used the ComponentResourceKey
to provide unique key names to the resources. This helps in avoiding name collisions. A ComponentResourceKey
has
two properties -TypeInTargetAssembly
(of type System.Type
) and ResourceId
(of type System.String
).
The SparkWindow
is surrounded by two borders - the OuterBorder and the InnerBorder. The window
content lies within the InnerBorder. The color, corner radius and thickness of these borders can be set using the properties
of the SparkWindow
(see SparkWindow Properties
table below).
Whenever the Title
property of the SparkWindow
is set, it animates the title. This is achieved by listening
to the Binding.TargetUpdated
RoutedEvent.
<TextBlock Name="PART_TitleText"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"
Foreground="{TemplateBinding Foreground}"
FontStyle="{TemplateBinding FontStyle}"
VerticalAlignment="Top"
Margin="{Binding Path=TitleMargin, RelativeSource={RelativeSource
FindAncestor,AncestorType={x:Type wpfspark:SparkWindow}}}"
Effect="{Binding Path=TitleEffect, RelativeSource={RelativeSource
FindAncestor,AncestorType={x:Type wpfspark:SparkWindow}}}"
Text="{Binding Path=Title, RelativeSource={RelativeSource
FindAncestor,AncestorType={x:Type wpfspark:SparkWindow}},
NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.3"
Storyboard.TargetProperty="Margin"
From="200,-30,0,0"
To="0,-30,0,0">
<ThicknessAnimation.EasingFunction>
<ExponentialEase EasingMode="EaseOut"
Exponent="2"></ExponentialEase>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
Override metadata
Next, to tell WPF that the default style has to be picked up from the above resource dictionary, I had to override the metadata for DefaultStyleKeyProperty
in the static constructor for the SparkWindow
class.
static SparkWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkWindow),
new FrameworkPropertyMetadata(typeof(SparkWindow)));
}
Set the ThemeInfo attribute
To ensure that the default style for SparkWindow
is picked up from the containing assembly (WPFSpark.dll) itself,
I modified the ThemeInfo
attribute defined in AssemblyInfo.cs.
[assembly: ThemeInfo(
ResourceDictionaryLocation.None,
ResourceDictionaryLocation.SourceAssembly
)]
The first parameter tells where the theme specific resource dictionaries are located and the second parameter tells where the generic resource dictionary is located.
Both parameters are of type ResourceDictionaryLocation
. Setting the second parameter to ResourceDictionaryLocation.SourceAssembly
tells
WPF to look for the generic resource dictionary within WPFSpark.dll.
Defining the Template Parts
The control logic for SparkWindow
(defined in SparkWindow.cs) requires access to some of the elements defined in the default style.
Therefore, as per the convention, these controls are given descriptive names starting with "PART_
" in the default template.
Then add TemplatePartAttribute
to the SparkWindow
class.
[TemplatePart(Name = "PART_About", Type = typeof(Button))]
[TemplatePart(Name = "PART_Minimize", Type = typeof(Button))]
[TemplatePart(Name = "PART_Close", Type = typeof(Button))]
public class SparkWindow : Window
{
...
Then, override the OnApplyTemplate()
method to find the required control using the GetTemplateChild()
method.
Here is the control logic defined in SparkWindow.cs:
namespace WPFSpark
{
[TemplatePart(Name = "PART_About", Type = typeof(Button))]
[TemplatePart(Name = "PART_Minimize", Type = typeof(Button))]
[TemplatePart(Name = "PART_Close", Type = typeof(Button))]
public class SparkWindow : Window
{
#region Fields
Button aboutButton = null;
Button minimizeButton = null;
Button maximizeButton = null;
Button closeButton = null;
Border titleBar = null;
#endregion
#region Dependency Properties
...
#endregion
#region Construction / Initialization
static SparkWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkWindow),
new FrameworkPropertyMetadata(typeof(SparkWindow)));
}
public SparkWindow()
{
this.WindowFrameMode = WindowMode.Pane;
}
#endregion
#region Overrides
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Unsubscribe();
GetTemplateParts();
}
protected override void OnClosing(CancelEventArgs e)
{
if (aboutButton != null)
aboutButton.Click -= OnAbout;
if (minimizeButton != null)
minimizeButton.Click -= OnMinimize;
if (maximizeButton != null)
maximizeButton.Click -= OnMaximize;
if (closeButton != null)
closeButton.Click -= OnClose;
if (titleBar != null)
titleBar.MouseLeftButtonDown -= OnTitleBarMouseDown;
base.OnClosing(e);
}
#endregion
#region Helpers
private void Unsubscribe()
{
if (aboutButton != null)
{
aboutButton.Click -= OnAbout;
}
if (minimizeButton != null)
{
minimizeButton.Click -= OnMinimize;
}
if (maximizeButton != null)
{
maximizeButton.Click -= OnMaximize;
}
if (closeButton != null)
{
closeButton.Click -= OnClose;
}
if (titleBar != null)
{
titleBar.MouseLeftButtonDown -= OnTitleBarMouseDown;
}
}
protected void GetTemplateParts()
{
aboutButton = GetChildControl<Button>("PART_About");
if (aboutButton != null)
{
aboutButton.Click += new RoutedEventHandler(OnAbout);
}
minimizeButton = GetChildControl<Button>("PART_Minimize");
if (minimizeButton != null)
{
minimizeButton.Click += new RoutedEventHandler(OnMinimize);
}
maximizeButton = GetChildControl<Button>("PART_Maximize");
if (maximizeButton != null)
{
maximizeButton.Click += new RoutedEventHandler(OnMaximize);
}
closeButton = GetChildControl<Button>("PART_Close");
if (closeButton != null)
{
closeButton.Click += new RoutedEventHandler(OnClose);
}
titleBar = GetChildControl<Border>("PART_TitleBar");
if (titleBar != null)
{
titleBar.MouseLeftButtonDown += new MouseButtonEventHandler(OnTitleBarMouseDown);
}
TextBlock tb = this.GetChildControl<TextBlock>("PART_TitleText");
if (tb == null)
return;
UpdateTriggerMargin(tb, TitleMargin);
UpdateWindowFrame(WindowFrameMode);
UpdateAboutButton(IsAboutEnabled);
}
private void UpdateWindowFrame(WindowMode winMode)
{
switch (winMode)
{
case WindowMode.CanClose:
case WindowMode.PaneCanClose:
if (minimizeButton != null)
minimizeButton.Visibility = Visibility.Collapsed;
if (maximizeButton != null)
maximizeButton.Visibility = Visibility.Collapsed;
break;
case WindowMode.Pane:
case WindowMode.CanMinimize:
default:
if (minimizeButton != null)
{
minimizeButton.Visibility = Visibility.Visible;
Grid.SetColumn(minimizeButton, 3);
}
if (maximizeButton != null)
maximizeButton.Visibility = Visibility.Collapsed;
break;
case WindowMode.CanMaximize:
if (minimizeButton != null)
{
minimizeButton.Visibility = Visibility.Visible;
Grid.SetColumn(minimizeButton, 2);
}
if (maximizeButton != null)
{
maximizeButton.Visibility = Visibility.Visible;
}
break;
}
if ((WindowFrameMode == WindowMode.Pane) || (WindowFrameMode == WindowMode.PaneCanClose))
{
this.WindowState = WindowState.Maximized;
}
}
private void UpdateAboutButton(bool isEnabled)
{
if (aboutButton == null)
return;
if (IsAboutEnabled)
{
aboutButton.Visibility = Visibility.Visible;
switch (WindowFrameMode)
{
case WindowMode.CanClose:
case WindowMode.PaneCanClose:
Grid.SetColumn(aboutButton, 3);
break;
case WindowMode.Pane:
case WindowMode.CanMinimize:
Grid.SetColumn(aboutButton, 2);
break;
case WindowMode.CanMaximize:
default:
Grid.SetColumn(aboutButton, 1);
break;
}
}
else
{
aboutButton.Visibility = Visibility.Collapsed;
}
}
protected T GetChildControl<T>(string ctrlName) where T : DependencyObject
{
T ctrl = GetTemplateChild(ctrlName) as T;
return ctrl;
}
private void UpdateTriggerMargin(TextBlock tb, Thickness margin)
{
if ((tb.Triggers == null) || (tb.Triggers.Count == 0))
return;
foreach (EventTrigger trigger in tb.Triggers)
{
if (trigger.RoutedEvent.Name == "TargetUpdated")
{
if ((trigger.Actions != null) && (trigger.Actions.Count > 0))
{
BeginStoryboard bsb = trigger.Actions[0] as BeginStoryboard;
if (bsb != null)
{
foreach (Timeline timeLine in bsb.Storyboard.Children)
{
ThicknessAnimation anim = timeLine as ThicknessAnimation;
if (anim != null)
{
Thickness startThickness =
new Thickness(margin.Left + 200, margin.Top,
margin.Right, margin.Bottom);
anim.SetValue(ThicknessAnimation.FromProperty, startThickness);
anim.SetValue(ThicknessAnimation.ToProperty, margin);
}
}
}
}
}
}
}
private void ToggleMaximize()
{
WindowState = (WindowState == WindowState.Maximized) ?
WindowState.Normal : WindowState.Maximized;
}
#endregion
#region Event Handlers
void OnTitleBarMouseDown(object sender, MouseButtonEventArgs e)
{
if (WindowFrameMode == WindowMode.CanMaximize && e.ClickCount == 2)
{
ToggleMaximize();
return;
}
this.DragMove();
}
protected virtual void OnAbout(object sender, RoutedEventArgs e)
{
}
protected virtual void OnMinimize(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
protected virtual void OnMaximize(object sender, RoutedEventArgs e)
{
ToggleMaximize();
}
protected virtual void OnClose(object sender, RoutedEventArgs e)
{
Close();
}
#endregion
}
}
SparkWindow properties
Dependency Property | Type | Description | Default Value |
InnerBorderBrush |
Brush |
Gets or sets the Brush for the InnerBorder of the SparkWindow . |
Brushes.White |
InnerBorderCornerRadius |
CornerRadius |
Gets or sets the CornerRadius of the InnerBorder of the SparkWindow. |
0,0,0,0 |
InnerBorderThickness |
Thickness |
Gets or sets the thickness of the InnerBorder of the SparkWindow. |
0,0,0,0 |
IsAboutEnabled |
Boolean |
Gets or sets whether the About button should be displayed in the titlebar. |
False |
OuterBorderBrush |
Brush |
Gets or sets the Brush for the OuterBorder of the SparkWindow . |
Brushes.Black |
OuterBorderCornerRadius |
CornerRadius |
Gets or sets the CornerRadius of the InnerBorder of the SparkWindow . |
0,0,0,0 |
OuterBorderThickness |
Thickness |
Gets or sets the thickness of the OuterBorder of the SparkWindow . |
0,0,0,0 |
TitleMargin |
Margin |
Gets or sets the Margin for the Title of the SparkWindow . |
0,0,0,0 |
TitleEffect |
Effect |
Gets or sets the various effects (DropShadowEffect , BlurEffect , etc.) for the Title of the SparkWindow . |
null |
WindowFrameMode |
WPFSpark.WindowMode |
Gets or sets the WindowMode of the SparkWindow . |
WPFSpark.WindowMode.Pane |
Using SparkWindow in your application
In order to use SparkWindow
in your application, you must follow these steps:
- Add a reference to WPFSpark.dll to your application project.
- Right-click on your project and select Add > Window. Give it a name.
- In the XAML and the code-behind for the newly added window, change the base class to
SparkWindow
.
<wpfspark:SparkWindow x:Class="WpfApplication2.CustomWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfspark="clr-namespace:WPFSpark;assembly=WPFSpark"
Title="CustomWindow"
Height="300"
Width="300"
WindowFrameMode="Pane"
InnerBorderBrush="White"
InnerBorderCornerRadius="2"
InnerBorderThickness="1"
OuterBorderBrush="Black"
OuterBorderCornerRadius="2"
OuterBorderThickness="1"
IsAboutEnabled="True">
<Grid>
</Grid>
</wpfspark:SparkWindow>
using WPFSpark;
namespace WpfApplication2
{
public partial class CustomWindow : SparkWindow
{
public CustomWindow()
{
InitializeComponent();
}
}
}
- If you have enabled the About button, then you can handle its click by overriding the
OnAbout
method. Similarly, you can override
the OnMinimize
, OnMaximize
, OnClosing
methods to handle the minimize, maximize, and close operations.
protected override void OnAbout(object sender, RoutedEventArgs e)
{
base.OnAbout(sender, e);
}
protected override void OnMinimize(object sender, RoutedEventArgs e)
{
base.OnMinimize(sender, e);
}
protected override void OnMaximize(object sender, RoutedEventArgs e)
{
base.OnMaximize(sender, e);
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
}
Providing your own Style/Template for SparkWindow
It is possible to override the Style or Template of the system buttons of SparkWindow
(or of SparkWindow
itself).
If you are providing a new template for the buttons, you also need to provide a new template for the SparkWindow
, in which you will be referring
to the new styles for the system buttons. When you override the template of SparkWindow
, you must ensure that the buttons have the same name as defined
in the original template of SparkWindow
(i.e., PART_About
, PART_Minimize
, PART_Maximize
, PART_Close
),
otherwise SparkWindow
will not be able to link the Click
event of these buttons to their respective functionality. Here is an example,
where I have overridden the button templates to display glyphs from the fonts Wingdings and Wingdings 2 as their content:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfspark="clr-namespace:WPFSpark;assembly=WPFSpark">
<Style x:Key="CustomMinimizeButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas Height="16" Width="16">
<Rectangle x:Name="BorderRect"
Width="15"
Height="15"
Stroke="Transparent"
Fill="Transparent"
StrokeThickness="0.5"></Rectangle>
<TextBlock x:Name="MinText"
FontFamily="Wingdings"
Foreground="White"
FontSize="14"
Text="ê"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver"
Value="true">
<Setter TargetName="BorderRect"
Property="Stroke"
Value="Gray"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99FFFFFF"
Offset="0"></GradientStop>
<GradientStop Color="Black"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="MinText"
Property="Foreground"
Value="LawnGreen"></Setter>
<Setter TargetName="BorderRect"
Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="5"></OuterGlowBitmapEffect>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed"
Value="true">
<Setter TargetName="MinText"
Property="Foreground"
Value="LawnGreen"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99333333"
Offset="0"></GradientStop>
<GradientStop Color="#99FFFFFF"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomMaximizeButtonStyle"
TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Viewbox HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Canvas Height="16"
Width="16">
<Rectangle x:Name="BorderRect"
Width="15"
Height="15"
Stroke="Transparent"
Fill="Transparent"
StrokeThickness="0.5"></Rectangle>
<TextBlock x:Name="MinText"
FontFamily="Wingdings"
Foreground="White"
FontSize="14"
Text="é"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver"
Value="true">
<Setter TargetName="BorderRect"
Property="Stroke"
Value="Gray"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99FFFFFF"
Offset="0"></GradientStop>
<GradientStop Color="Black"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="MinText"
Property="Foreground"
Value="LawnGreen"></Setter>
<Setter TargetName="BorderRect"
Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="5"></OuterGlowBitmapEffect>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed"
Value="true">
<Setter TargetName="MinText"
Property="Foreground"
Value="LawnGreen"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99333333"
Offset="0"></GradientStop>
<GradientStop Color="#99FFFFFF"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomCloseButtonStyle"
TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Viewbox HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Canvas Height="16"
Width="16">
<Rectangle x:Name="BorderRect"
Width="15"
Height="15"
Stroke="Transparent"
Fill="Transparent"
StrokeThickness="0.5"></Rectangle>
<TextBlock x:Name="MinText"
FontFamily="Wingdings 2"
Foreground="White"
FontSize="14"
Text="Ñ"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver"
Value="true">
<Setter TargetName="BorderRect"
Property="Stroke"
Value="Gray"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99FFFFFF"
Offset="0"></GradientStop>
<GradientStop Color="Black"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="MinText"
Property="Foreground"
Value="LawnGreen"></Setter>
<Setter TargetName="BorderRect"
Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="5"></OuterGlowBitmapEffect>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed"
Value="true">
<Setter TargetName="MinText"
Property="Foreground"
Value="LawnGreen"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99333333"
Offset="0"></GradientStop>
<GradientStop Color="#99FFFFFF"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomAboutButtonStyle"
TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Viewbox HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Canvas Height="16"
Width="16">
<Rectangle x:Name="BorderRect"
Width="15"
Height="15"
Stroke="Transparent"
Fill="Transparent"
StrokeThickness="0.5"></Rectangle>
<TextBlock x:Name="MinText"
FontFamily="Wingdings"
Foreground="White"
FontSize="14"
Text="µ"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver"
Value="true">
<Setter TargetName="BorderRect"
Property="Stroke"
Value="Gray"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99FFFFFF"
Offset="0"></GradientStop>
<GradientStop Color="Black"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="MinText"
Property="Foreground"
Value="LawnGreen"></Setter>
<Setter TargetName="BorderRect"
Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="LawnGreen"
GlowSize="5"></OuterGlowBitmapEffect>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed"
Value="true">
<Setter TargetName="MinText"
Property="Foreground"
Value="LawnGreen"></Setter>
<Setter TargetName="BorderRect"
Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#99333333"
Offset="0"></GradientStop>
<GradientStop Color="#99FFFFFF"
Offset="0.7"></GradientStop>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomSparkWindowStyle" TargetType="{x:Type wpfspark:SparkWindow}">
<Setter Property="AllowsTransparency"
Value="False"></Setter>
<Setter Property="ResizeMode"
Value="NoResize"></Setter>
<Setter Property="MinHeight"
Value="100"></Setter>
<Setter Property="MinWidth"
Value="200"></Setter>
<Setter Property="MaxWidth"
Value="{DynamicResource {x:Static SystemParameters.MaximizedPrimaryScreenWidthKey}}"></Setter>
<Setter Property="MaxHeight"
Value="{DynamicResource {x:Static SystemParameters.MaximizedPrimaryScreenHeightKey}}"></Setter>
<Setter Property="WindowStyle"
Value="None"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type wpfspark:SparkWindow}">
<Border Name="OuterFrame"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="{Binding Path=OuterBorderBrush,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
BorderThickness="{Binding Path=OuterBorderThickness,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
CornerRadius="{Binding Path=OuterBorderCornerRadius,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
SnapsToDevicePixels="True"
Background="Transparent">
<Border Name="windowFrame"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="{Binding Path=InnerBorderBrush, RelativeSource=
{RelativeSource FindAncestor,AncestorType=
{x:Type wpfspark:SparkWindow}}}"
BorderThickness="{Binding Path=InnerBorderThickness,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
CornerRadius="{Binding Path=InnerBorderCornerRadius,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
SnapsToDevicePixels="True"
Background="{TemplateBinding Background}">
<Grid ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="2"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="2"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="1"
Grid.RowSpan="3"
Grid.Column="1"
Margin="0,0,0,0">
<TextBlock Name="PART_TitleText"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"
Foreground="{TemplateBinding Foreground}"
FontStyle="{TemplateBinding FontStyle}"
VerticalAlignment="Top"
Margin="{Binding Path=TitleMargin,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
Effect="{Binding Path=TitleEffect,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type wpfspark:SparkWindow}}}"
Text="{Binding Path=Title, RelativeSource=
{RelativeSource FindAncestor,AncestorType=
{x:Type wpfspark:SparkWindow}},
NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.3"
Storyboard.TargetProperty="Margin"
From="400,-30,0,0"
To="0,-30,0,0">
<ThicknessAnimation.EasingFunction>
<ExponentialEase EasingMode="EaseOut"
Exponent="2"></ExponentialEase>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</Grid>
<Border Name="PART_TitleBar"
Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
Grid.ColumnSpan="2"
Background="Black"
Opacity="0" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="5" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="1"
Grid.RowSpan="4"
Grid.Column="1"
Margin="0,0,0,0">
</Grid>
<Button Name="PART_About"
Margin="0,5,0,0"
Grid.Column="1"
Style="{StaticResource CustomAboutButtonStyle}">
</Button>
<Button Name="PART_Minimize"
Margin="0,5,0,0"
Grid.Column="2"
Style="{StaticResource CustomMinimizeButtonStyle}">
</Button>
<Button Name="PART_Maximize"
Margin="0,5,0,0"
Grid.Column="3"
Style="{StaticResource CustomMaximizeButtonStyle}">
</Button>
<Button Name="PART_Close"
Margin="0,5,0,0"
Grid.Column="4"
Style="{StaticResource CustomCloseButtonStyle}"></Button>
</Grid>
-->
<AdornerDecorator Grid.Row="3" Grid.Column="0">
<ContentPresenter />
</AdornerDecorator>
</Grid>
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
You can use the above style on any SparkWindow
.
EndPoint
In the default style for SparkWindow
, the ResizeMode
is set to NoResize
as SparkWindow
does not support
resizing currently. I am planning to bring this feature in the future version of WPFSpark
.
History
- December 21, 2011: WPFSpark v1.0 released.