xwt/Xwt.WPF/Xwt.WPFBackend/WindowBackend.cs

315 строки
9.3 KiB
C#

//
// WindowBackend.cs
//
// Author:
// Carlos Alberto Cortez <calberto.cortez@gmail.com>
// Luís Reis <luiscubal@gmail.com>
//
// Copyright (c) 2011 Carlos Alberto Cortez
// Copyright (c) 2012 Luís Reis
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using SW = System.Windows;
using Xwt.Backends;
namespace Xwt.WPFBackend
{
public class WindowBackend : WindowFrameBackend, IWindowBackend
{
protected Grid rootPanel;
public System.Windows.Controls.Menu mainMenu;
MenuBackend mainMenuBackend;
FrameworkElement widget;
DockPanel contentBox;
public WindowBackend ()
{
base.Window = new WpfWindow ();
Window.UseLayoutRounding = true;
rootPanel = CreateMainGrid ();
contentBox = new DockPanel ();
Window.Content = rootPanel;
Grid.SetColumn (contentBox, 0);
Grid.SetRow (contentBox, 1);
rootPanel.Children.Add (contentBox);
}
new WpfWindow Window
{
get { return (WpfWindow)base.Window; }
}
public override void Initialize ()
{
base.Initialize ();
Window.Frontend = (Window) Frontend;
}
// A Grid with a single column, and two rows (menu and child control).
static Grid CreateMainGrid ()
{
var grid = new Grid ();
grid.ColumnDefinitions.Add (new ColumnDefinition ());
var menuRow = new RowDefinition () { Height = GridLength.Auto }; // Only take the menu requested space.
var contentRow = new RowDefinition (); // Take all the remaining space (default).
grid.RowDefinitions.Add (menuRow);
grid.RowDefinitions.Add (contentRow);
return grid;
}
public override bool HasMenu {
get { return mainMenu != null; }
}
public override Rectangle Bounds
{
get
{
return Window.ClientBounds;
}
set
{
Window.ClientBounds = value;
Context.InvokeUserCode (delegate
{
EventSink.OnBoundsChanged (Bounds);
});
}
}
public void SetChild (IWidgetBackend child)
{
if (widget != null) {
contentBox.Children.Remove (widget);
widget.SizeChanged -= ChildSizeChanged;
}
widget = ((IWpfWidgetBackend)child).Widget;
contentBox.Children.Add (widget);
// This event is subscribed to ensure that the content of the
// widget is reallocated when the widget gets a new size. This
// is not a problem when setting the child before showing the
// window, but it may be a problem if the window is already visible.
widget.SizeChanged += ChildSizeChanged;
if (child != null)
UpdateChildPlacement (child);
}
public virtual void UpdateChildPlacement (IWidgetBackend childBackend)
{
WidgetBackend.SetChildPlacement (childBackend);
}
void ChildSizeChanged (object o, SizeChangedEventArgs args)
{
((Window)Frontend).Content.Surface.Reallocate ();
}
public void SetMainMenu (IMenuBackend menu)
{
if (mainMenu != null) {
mainMenuBackend.ParentWindow = null;
rootPanel.Children.Remove (mainMenu);
}
if (menu == null) {
mainMenu = null;
mainMenuBackend = null;
return;
}
var menuBackend = (MenuBackend)menu;
var m = new System.Windows.Controls.Menu ();
foreach (var item in menuBackend.Items)
m.Items.Add (item.Item);
Grid.SetColumn (m, 0);
Grid.SetRow (m, 0);
rootPanel.Children.Add (m);
mainMenu = m;
mainMenuBackend = menuBackend;
mainMenuBackend.ParentWindow = this;
}
public void SetPadding (double left, double top, double right, double bottom)
{
contentBox.Margin = new Thickness (left, top, right, bottom);
}
public virtual void SetMinSize (Size s)
{
Window.SetMinSize (s);
}
public virtual void GetMetrics (out Size minSize, out Size decorationSize)
{
minSize = decorationSize = Size.Zero;
if (mainMenu != null) {
mainMenu.InvalidateMeasure ();
mainMenu.Measure (new System.Windows.Size (double.PositiveInfinity, double.PositiveInfinity));
var h = mainMenu.DesiredSize.Height;
decorationSize.Height = h;
}
}
protected override void OnResizeModeChanged ()
{
Window.ResetBorderSize ();
}
}
class WpfWindow : System.Windows.Window
{
public Window Frontend;
bool borderCalculated;
WidgetSpacing frameBorder;
Size minSizeRequested;
double initialX, initialY;
public WpfWindow ()
{
// We initially use WidthAndHeight mode since we need to calculate the size
// of the window borders
SizeToContent = System.Windows.SizeToContent.WidthAndHeight;
}
public void ResetBorderSize ()
{
// Called when the size of the border may have changed
if (borderCalculated) {
var r = ClientBounds;
initialX = Left + frameBorder.Left;
initialY = Top + frameBorder.Top;
borderCalculated = false;
ClientBounds = r;
}
}
public void SetMinSize (Size size)
{
if (borderCalculated) {
if (size.Width != -1)
MinWidth = size.Width + frameBorder.HorizontalSpacing;
if (size.Height != -1)
MinHeight = size.Height + frameBorder.VerticalSpacing;
}
else
minSizeRequested = size;
}
public Rectangle ClientBounds
{
get
{
var c = (FrameworkElement)Content;
var w = double.IsNaN (c.Width) ? c.ActualWidth : c.Width;
var h = double.IsNaN (c.Height) ? c.ActualHeight : c.Height;
if (PresentationSource.FromVisual (c) == null)
return new Rectangle (initialX, initialY, w, h);
else {
var p = c.PointToScreen (new SW.Point (0, 0));
return new Rectangle (p.X, p.Y, w, h);
}
}
set
{
// Don't use WindowFrameBackend.ToNonClientRect to calculate the client area because that method is not reliable (see comment in ToNonClientRect).
// Instead, we use our own border size calculation method, which is:
// 1) Set the Width and Height of the widget to the desired client rect, and set SizeToContent property to WidthAndHeight
// 2) The window will resize itself to fit the content
// 3) When the size of the window is set (OnRenderSizeChanged event), calculate the border by comparing the screen position of
// the root content with the screen position of the window.
if (borderCalculated) {
// Border size already calculated. Just do the math.
Left = value.Left - frameBorder.Left;
Top = value.Top - frameBorder.Top;
Width = value.Width + frameBorder.HorizontalSpacing;
Height = value.Height + frameBorder.VerticalSpacing;
}
else {
// store the required size and position and enable SizeToContent mode. When the window size is set, we'll calculate the border size.
var c = (FrameworkElement)Content;
initialX = value.Left;
initialY = value.Top;
c.Width = value.Width;
c.Height = value.Height;
SizeToContent = System.Windows.SizeToContent.WidthAndHeight;
}
}
}
protected override System.Windows.Size ArrangeOverride (System.Windows.Size arrangeBounds)
{
var s = base.ArrangeOverride (arrangeBounds);
if (Frontend.Content != null)
Frontend.Content.Surface.Reallocate ();
return s;
}
protected override void OnRenderSizeChanged (SizeChangedInfo sizeInfo)
{
// Once the physical size of the window has been set we can calculate
// the size of the borders, which will be used for further client/non client
// area coordinate conversions
CalcBorderSize (sizeInfo.NewSize.Width, sizeInfo.NewSize.Height);
base.OnRenderSizeChanged (sizeInfo);
}
void CalcBorderSize (double windowWidth, double windowHeight)
{
if (borderCalculated)
return;
var c = (FrameworkElement)Content;
var p = c.PointToScreen (new SW.Point (0, 0));
var left = p.X - Left;
var top = p.Y - Top;
frameBorder = new WidgetSpacing (left, top, windowWidth - c.ActualWidth - left, windowHeight - c.ActualHeight - top);
borderCalculated = true;
Left = initialX - left;
Top = initialY - top;
SetMinSize (minSizeRequested);
// Border size calculation done and we can go back to Manual resize mode.
// From now on, the content has to adapt to the size of the window.
SizeToContent = System.Windows.SizeToContent.Manual;
c.Width = double.NaN;
c.Height = double.NaN;
}
}
}