I have been working to modify the tool I wrote for building the Perenthia world. The tool started life as a WinForms application, migrated to a WPF application but is now a part of the Silverlight Client UI. Since all of the actions are role based and controlled with permission on the server having it in the main client UI allows me to make use of all of the framework already in place for Perenthia.
The world builder UI connects to the server via WCF services tailored for world building. What this will also give me is the ability to open up the world builder to allow users to create custom dungeons, towns, etc.
Some features of the world builder are a background map of the world so I know where to place rooms, zoom capabilities that scale the map, draggable map and the ability to drag items, creatures, NPCs, etc. onto rooms on the map and configure the properties of each.
4/29/2009
Here is a draggable Window control I wrote in Silverlight for Perenthia. A screen shot of the Window being used can be seen at http://cameronalbert.com/post/2008/11/19/Silverlight-Game-Controls.aspx
Here is the XAML from my generics.xaml file:
<!-- Default style for Lionsguard.Window -->
<Style TargetType="lg:Window"><Setter Property="Background" Value="#99000000" /><Setter Property="Foreground" Value="#FFFFFFFF" /><Setter Property="BorderBrush" Value="#FFC38312" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="lg:Window"><Grid x:Name="RootElement"><Border HorizontalAlignment="Stretch" Margin="0,24,0,0" Padding="4,4,4,4" VerticalAlignment="Stretch" BorderThickness="2,2,2,2" CornerRadius="0,0,4,4"><ContentControl x:Name="ContentElement" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/></Border><Border Height="24" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Top" CornerRadius="4,4,0,0" BorderThickness="2,2,2,0" x:Name="TitleBarElement" /><Button Height="20" HorizontalAlignment="Right" Margin="0,3,3,0" VerticalAlignment="Top" Width="20" Content="X" FontWeight="Bold" FontFamily="Trebuchet MS" FontSize="14" x:Name="CloseButtonElement"/><TextBlock Height="20" Margin="8,3,27,0" VerticalAlignment="Top" Text="Title" TextWrapping="Wrap" x:Name="TitleLabelElement" FontFamily="Georgia" FontSize="16" FontWeight="Bold" HorizontalAlignment="Left"/></Grid></ControlTemplate></Setter.Value></Setter></Style>
And here is the code for the class:
using System;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Lionsguard
{
[TemplatePart(Name = "RootElement", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "ContentElement", Type = typeof(ContentControl))]
[TemplatePart(Name = "TitleBarElement", Type = typeof(Border))]
[TemplatePart(Name = "CloseButtonElement", Type = typeof(Button))]
[TemplatePart(Name = "TitleLabelElement", Type = typeof(TextBlock))]
[ContentProperty("Content")]
public class Window : Control
{
public FrameworkElement RootElement { get; set; }
private ContentControl ContentElement { get; set; }
private Border TitleBarElement { get; set; }
private Button CloseButtonElement { get; set; }
private TextBlock TitleLabelElement { get; set; }
private Point MousePosition { get; set; }
/// <summary>
/// Gets or sets the Content of the Window.
/// </summary>
public UIElement Content
{
get { return (UIElement)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(UIElement), typeof(Window), new PropertyMetadata(null, new PropertyChangedCallback(Window.OnContentPropertyChanged)));
private static void OnContentPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as Window).SetControlValues();
}
/// <summary>
/// Gets or sets the Title displayed in the title bar of the window.
/// </summary>
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(Window), new PropertyMetadata("Title", new PropertyChangedCallback(Window.OnTitlePropertyChanged)));
private static void OnTitlePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as Window).SetControlValues();
}
/// <summary>
/// Gets or sets a value indicating whether or not the close button of the title bar should be displayed.
/// </summary>
public bool ShowCloseButton
{
get { return (bool)GetValue(ShowCloseButtonProperty); }
set { SetValue(ShowCloseButtonProperty, value); }
}
public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.Register("ShowCloseButton", typeof(bool), typeof(Window), new PropertyMetadata(true, new PropertyChangedCallback(Window.OnShowCloseButtonPropertyChanged)));
private static void OnShowCloseButtonPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as Window).SetControlValues();
}
/// <summary>
/// Gets a value indicating whether or not the window is currently open and visible.
/// </summary>
public bool IsOpen
{
get { return this.Visibility == Visibility.Visible; }
}
/// <summary>
/// An event that is raised when the window is closed.
/// </summary>
public event EventHandler Closed = delegate { };
/// <summary>
/// Initializes a new instance of the Lionsguard.Window class.
/// </summary>
public Window()
{
this.DefaultStyleKey = typeof(Window);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.RootElement = base.GetTemplateChild("RootElement") as FrameworkElement;
this.ContentElement = base.GetTemplateChild("ContentElement") as ContentControl;
this.TitleBarElement = base.GetTemplateChild("TitleBarElement") as Border;
this.CloseButtonElement = base.GetTemplateChild("CloseButtonElement") as Button;
this.TitleLabelElement = base.GetTemplateChild("TitleLabelElement") as TextBlock;
if (this.RootElement != null)
{
this.RootElement.MouseLeftButtonDown += new MouseButtonEventHandler(OnRootElementMouseLeftButtonDown);
this.TitleBarElement.MouseLeftButtonDown += new MouseButtonEventHandler(OnTitleBarMouseLeftButtonDown);
this.TitleBarElement.MouseEnter += new MouseEventHandler(OnTitleBarMouseEnter);
this.TitleBarElement.MouseLeave += new MouseEventHandler(OnTitleBarMouseLeave);
this.TitleLabelElement.MouseLeftButtonDown += new MouseButtonEventHandler(OnTitleBarMouseLeftButtonDown);
this.TitleLabelElement.MouseEnter += new MouseEventHandler(OnTitleBarMouseEnter);
this.TitleLabelElement.MouseLeave += new MouseEventHandler(OnTitleBarMouseLeave);
this.CloseButtonElement.Click += new RoutedEventHandler(OnCloseButtonClick);
this.SetControlValues();
}
}
private void SetControlValues()
{
if (this.ContentElement != null && this.Content != null)
{
this.ContentElement.Content = this.Content;
}
if (this.TitleLabelElement != null)
{
this.TitleLabelElement.Text = this.Title;
ToolTipService.SetToolTip(this.TitleLabelElement, this.Title);
}
if (this.CloseButtonElement != null)
{
if (this.ShowCloseButton) this.CloseButtonElement.Visibility = Visibility.Visible;
else this.CloseButtonElement.Visibility = Visibility.Collapsed;
}
}
private void OnRootElementMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Set the Z-Index of the window to the top most position.
this.BringToFront();
}
private void OnCloseButtonClick(object sender, RoutedEventArgs e)
{
this.Close();
}
private void OnTitleBarMouseLeave(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.Arrow;
}
private void OnTitleBarMouseEnter(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.Hand;
}
private void OnTitleBarMouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition(null);
// Prevent the mouse from moving outside the bounds of the parent canvas.
if (position.X <= 30) position.X = 30;
if (position.Y <= 30) position.Y = 30;
Canvas parent = this.Parent as Canvas;
if (parent != null)
{
if (position.X >= (parent.Width - 30)) position.X = parent.Width - 30;
if (position.Y >= (parent.Height - 30)) position.Y = parent.Height - 30;
}
System.Diagnostics.Debug.WriteLine("position = {0}", position);
double deltaX = position.X - this.MousePosition.X;
double deltaY = position.Y - this.MousePosition.Y;
Point newPosition = new Point(
((double)this.GetValue(Canvas.LeftProperty)) + deltaX,
((double)this.GetValue(Canvas.TopProperty)) + deltaY);
this.SetValue(Canvas.LeftProperty, newPosition.X);
this.SetValue(Canvas.TopProperty, newPosition.Y);
this.MousePosition = position;
}
private void OnTitleBarMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
this.TitleBarElement.MouseLeftButtonUp -= new MouseButtonEventHandler(OnTitleBarMouseLeftButtonUp);
this.TitleBarElement.MouseMove -= new MouseEventHandler(OnTitleBarMouseMove);
this.TitleLabelElement.MouseLeftButtonUp -= new MouseButtonEventHandler(OnTitleBarMouseLeftButtonUp);
this.TitleLabelElement.MouseMove -= new MouseEventHandler(OnTitleBarMouseMove);
}
private void OnTitleBarMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.MousePosition = e.GetPosition(null);
this.Cursor = Cursors.Hand;
((UIElement)sender).CaptureMouse();
this.TitleBarElement.MouseLeftButtonUp += new MouseButtonEventHandler(OnTitleBarMouseLeftButtonUp);
this.TitleBarElement.MouseMove += new MouseEventHandler(OnTitleBarMouseMove);
this.TitleLabelElement.MouseLeftButtonUp += new MouseButtonEventHandler(OnTitleBarMouseLeftButtonUp);
this.TitleLabelElement.MouseMove += new MouseEventHandler(OnTitleBarMouseMove);
}
/// <summary>
/// Causes the current window to be re-z-indexed to the top most window.
/// </summary>
protected void BringToFront()
{
// Search the top most "Window" and swap z-indexes.
Panel parent = this.Parent as Panel; // Panel is the base for Canvas, Grid and StackPanel.
if (parent != null)
{
int currentZIndex = (int)this.GetValue(Canvas.ZIndexProperty);
var child = (from c in parent.Children where c is Window select c as Window).OrderByDescending(c => (int)c.GetValue(Canvas.ZIndexProperty)).FirstOrDefault();
if (child != null)
{
int topZIndex = (int)child.GetValue(Canvas.ZIndexProperty);
if (topZIndex == 0) topZIndex = 1; // If the value has not been set then just default it to 1.
if (topZIndex > currentZIndex)
{
this.SetValue(Canvas.ZIndexProperty, topZIndex);
child.SetValue(Canvas.ZIndexProperty, currentZIndex);
}
}
}
}
public void Show()
{
this.Visibility = Visibility.Visible;
// If content is present show that as well.
if (this.Content != null) this.Content.Visibility = Visibility.Visible;
}
public void Close()
{
this.Visibility = Visibility.Collapsed;
// If content is present hide that as well.
if (this.Content != null) this.Content.Visibility = Visibility.Collapsed;
this.Closed(this, EventArgs.Empty);
}
public void ToggleWindow()
{
if (this.Visibility == Visibility.Visible)
{
this.Close();
}
else
{
this.Show();
}
}
}
}
This is how I am using it within the Pernethia UI (The content of window is actually the Character Sheet as displayed in the screenshot from above):
Namespace: xmlns:Lionsguard="clr-namespace:Lionsguard;assembly=Lionsguard.Silverlight"
<Lionsguard:Window Height="375" x:Name="diagCharacterSheet" Visibility="Collapsed" Width="634" Canvas.Left="8" Canvas.Top="19" Title="Character Sheet" Style="{StaticResource WindowStyle}" d:IsLocked="True">
<Perenthia_Dialogs:CharacterSheetDialog x:Name="diagCharacterSheetContent" SkillChanged="diagCharacterSheetContent_SkillChanged"/></Lionsguard:Window>
Enjoy!
4/22/2009
I wrote some classes for handling drag and drop operations in Silverlight that I use within Perenthia. I thought I would go ahead and share them since I found them to be very useful in getting drag and drag working quickly in new projects.
The Drag and Drag classes consist of two interfaces (IDroppable, IDropContainer) and a manager class (DragDropManager). The interfaces are defined as such:
namespace Lionsguard
{
/// <summary>
/// Defines an object that can be dragged and dropped via the DragDropManager.
/// </summary>
public interface IDroppable
{
/// <summary>
/// An event that is raised when the IDroppable item begins the drag operation.
/// </summary>
event BeginDragEventHandler BeginDrag;
/// <summary>
/// Gets the UIElement that will server as the cursor replacement when dragging the current object.
/// </summary>
/// <returns></returns>
UIElement GetDragCursor();
}
/// <summary>
/// Defines an object that accept IDroppable items as part of a drop operation.
/// </summary>
public interface IDropContainer
{
/// <summary>
/// Handles the drop of the specified IDroppable item onto the container.
/// </summary>
/// <param name="item"></param>
void Drop(IDroppable item);
}
}
The DragDropManager (and and associated delegate and event args class) are defined as such:
namespace Lionsguard
{
/// <summary>
/// Represents a class used to manage drag and drop items.
/// </summary>
public class DragDropManager
{
private Panel Host { get; set; }
private Point MousePosition { get; set; }
private UIElement DragCursor { get; set; }
private bool HasProcessedEndDrag { get; set; }
private IDroppable Item { get; set; }
/// <summary>
/// Initializes a new instance of the DragDropManager class.
/// </summary>
/// <param name="host">The hosting panel where the drag cursor instance will be added.</param>
public DragDropManager(Panel host)
{
this.Host = host;
this.Host.LostFocus += new RoutedEventHandler(OnHostLostFocus);
}
private void OnHostLostFocus(object sender, RoutedEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorLostMouseCapture(object sender, MouseEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorLostFocus(object sender, RoutedEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
this.MousePosition = e.GetPosition(null);
this.EndDrag(true);
}
private void OnDragCursorMouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition(null);
double deltaX = position.X - this.MousePosition.X;
double deltaY = position.Y - this.MousePosition.Y;
Point newPosition = new Point(
((double)this.DragCursor.GetValue(Canvas.LeftProperty)) + deltaX,
((double)this.DragCursor.GetValue(Canvas.TopProperty)) + deltaY);
this.DragCursor.SetValue(Canvas.LeftProperty, newPosition.X);
this.DragCursor.SetValue(Canvas.TopProperty, newPosition.Y);
this.MousePosition = position;
}
/// <summary>
/// Begins a drag operation for the specified IDroppable instance starting at the specified mousePosition.
/// </summary>
/// <param name="droppable">The IDroppable instance to being dragging.</param>
/// <param name="mousePosition">The Point at which the drag operation will begin.</param>
public void BeginDrag(IDroppable droppable, Point mousePosition)
{
if (droppable != null && this.Host != null)
{
System.Diagnostics.Debug.WriteLine("Begin Drag...");
this.Item = droppable;
this.DragCursor = droppable.GetDragCursor();
this.DragCursor.LostFocus += new RoutedEventHandler(OnDragCursorLostFocus);
this.DragCursor.LostMouseCapture += new MouseEventHandler(OnDragCursorLostMouseCapture);
// Add the drag cursor to the host control and set the z-index very high.
this.DragCursor.SetValue(Canvas.ZIndexProperty, 5000);
this.Host.Children.Add(this.DragCursor);
this.Host.Cursor = Cursors.Hand;
this.MousePosition = mousePosition;
this.DragCursor.SetValue(Canvas.LeftProperty, this.MousePosition.X);
this.DragCursor.SetValue(Canvas.TopProperty, this.MousePosition.Y);
this.DragCursor.Visibility = Visibility.Visible;
this.HasProcessedEndDrag = false;
this.DragCursor.CaptureMouse();
this.DragCursor.MouseLeftButtonUp += new MouseButtonEventHandler(OnDragCursorMouseLeftButtonUp);
this.DragCursor.MouseMove += new MouseEventHandler(OnDragCursorMouseMove);
}
}
private void EndDrag(bool processDrop)
{
// This prevents the mouse up event or the end drag event firing more than once from causing the
// events to bubble up and the drop be handled more than once.
System.Diagnostics.Debug.WriteLine("End Drag...");
if (this.HasProcessedEndDrag) return;
this.HasProcessedEndDrag = true;
// Reshow the cursor and hide the source image.
this.Host.Cursor = Cursors.Arrow;
// Hide the drag cursor.
if (this.DragCursor != null)
{
this.DragCursor.Visibility = Visibility.Collapsed;
this.DragCursor.LostFocus -= new RoutedEventHandler(OnDragCursorLostFocus);
this.DragCursor.LostMouseCapture -= new MouseEventHandler(OnDragCursorLostMouseCapture);
this.DragCursor.MouseLeftButtonUp -= new MouseButtonEventHandler(OnDragCursorMouseLeftButtonUp);
this.DragCursor.MouseMove -= new MouseEventHandler(OnDragCursorMouseMove);
}
if (processDrop)
{
System.Diagnostics.Debug.WriteLine("Process Drop...");
// Search within host for an IDropContainer instance within the area of the current mouse position.
var container = (from v in VisualTreeHelper.FindElementsInHostCoordinates(this.MousePosition, this.Host)
where (v as IDropContainer) != null
select v as IDropContainer).FirstOrDefault();
if (container != null)
{
System.Diagnostics.Debug.WriteLine("Drop...");
container.Drop(this.Item);
}
}
}
}
/// <summary>
/// Provides a delegate for handling IDroppable.BeginDrag events.
/// </summary>
/// <param name="sender">The IDroppable initiating the drag operation.</param>
/// <param name="e">Event data for the operation.</param>
public delegate void BeginDragEventHandler(object sender, BeginDragEventArgs e);
/// <summary>
/// Represents event data for a BeginDrag event.
/// </summary>
public class BeginDragEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the IDroppable initiating the event.
/// </summary>
public IDroppable Droppable { get; set; }
/// <summary>
/// Gets or sets the Point at which the drag operation should begin.
/// </summary>
public Point MousePosition { get; set; }
}
}
private void AdminScreen_Loaded(object sender, RoutedEventArgs e){_dragDropManager = new DragDropManager(LayoutRoot);}
avatarItem.BeginDrag += new BeginDragEventHandler(OnDroppableBeginDrag);
When the IDroppable raises the BeginDrag event, on a left mouse button click, this method is called which causes the DragDropManager to being the drag operation for the current IDroppable.
private void OnDroppableBeginDrag(object sender, BeginDragEventArgs e)
{
if (_dragDropManager != null)
{
_dragDropManager.BeginDrag(e.Droppable, e.MousePosition);
}
}
When an IDroppable is dropped onto the very same control this method is executed:
#region IDropContainer Members
public void Drop(IDroppable item)
{
// Do stuff when the object is dropped...
}
#endregion
This is pretty much all there is to it.
4/21/2009
4/16/2009