Saturday, October 04, 2008

Radial Panel

In Applications = Code + Markup: A Guide To The Microsoft Windows Presentation Foundation, Charles Petzold showed us how to create a radial panel. A radial panel presents its children elements around the circumference of a circle. While good, the code did not address the case when there is only 1 child, causing it to behave oddly. There are also some bugs in painting the pie lines, with the lines cutting the elements instead of drawing along the sides. So I corrected the bugs and handled the edge case. But what's the use of such a fanciful radial panel? Personally, I used it to make a Firefox style busy icon. It can be used to make the pie menu too, though some code changes is needed to rotate the icons back. I believe you have much more creative idea up in your heads! Here comes the code

//-----------------------------------------------------------------------

// <copyright file="RadialPanel.cs" company="Jeow Li Huan">

// Copyright (c) Jeow Li Huan. All rights reserved.

// </copyright>

//-----------------------------------------------------------------------

 

using System;

using System.ComponentModel;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Media;

 

namespace TetonWhitewaterKayak.WinUI

{

    /// <summary>

    ///   Arranges child elements along the circumference of a circle.

    /// </summary>

    public class RadialPanel : Panel

    {

        /// <summary>

        ///   Identifies the RadialPanel.Orientation dependency property.

        /// </summary>

        public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(

            "Orientation",

            typeof(Orientation),

            typeof(RadialPanel),

            new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure));

 

        /// <summary>

        ///   Identifies the RadialPanel.ForegroundProperty dependency property.

        /// </summary>

        public static readonly DependencyProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner(typeof(RadialPanel), new FrameworkPropertyMetadata(SystemColors.ControlTextBrush, FrameworkPropertyMetadataOptions.Inherits));

 

        /// <summary>

        ///   Backing field for the ShowPieLines property.

        /// </summary>

        private bool showPieLines;

 

        /// <summary>

        ///   The angle of the slice that each child occupies.

        /// </summary>

        private double angleEach;

 

        /// <summary>

        ///   Size of the largest child.

        /// </summary>

        private Size sizeLargest;

 

        /// <summary>

        ///   Radius of the circle that surrounds the children.

        /// </summary>

        private double radius;

 

        /// <summary>

        ///   The distance from the top of any child to the center of the circle.

        /// </summary>

        private double outerEdgeFromCenter;

 

        /// <summary>

        ///   The distance from the bottom of any child to the center of the circle.

        /// </summary>

        private double innerEdgeFromCenter;

 

        /// <summary>

        ///   Gets or sets a brush that describes the foreground color. This is a dependency property.

        /// </summary>

        [Category("Appearance")]

        [Bindable(true)]

        public Brush Foreground

        {

            get { return (Brush)GetValue(ForegroundProperty); }

            set { SetValue(ForegroundProperty, value); }

        }

 

        /// <summary>

        ///   Gets or sets a value that indicates whether child elements span the circumference of the panel by their widths or by their heights.

        ///   This is a dependency property.

        /// </summary>

        [Category("Layout")]

        public Orientation Orientation

        {

            get { return (Orientation)GetValue(OrientationProperty); }

            set { SetValue(OrientationProperty, value); }

        }

 

        /// <summary>

        ///    Gets or sets a value indicating whether to draw lines along the circumference and

        ///    along the spooks that separates the child elements.

        /// </summary>

        [Category("Appearance")]

        [DefaultValue(false)]

        public bool ShowPieLines

        {

            set

            {

                if (this.showPieLines != value)

                    this.showPieLines = value;

                this.InvalidateVisual();

            }

 

            get

            {

                return this.showPieLines;

            }

        }

 

        /// <summary>

        ///   Measures the child elements of a RadialPanel in anticipation

        ///   of arranging them during the RadialPanel.ArrangeOverride(System.Windows.Size)

        ///   pass.

        /// </summary>

        /// <param name="sizeAvailable">

        ///   An upper limit <see cref="T:System.Windows.Size"/> that should not be exceeded.

        /// </param>

        /// <returns>

        ///   The <see cref="T:System.Windows.Size"/> that represents the desired size of the element.

        /// </returns>

        protected override Size MeasureOverride(Size sizeAvailable)

        {

            if (this.InternalChildren.Count == 0)

                return new Size();

 

            this.angleEach = 360.0 / this.InternalChildren.Count;

            this.sizeLargest = new Size();

 

            foreach (UIElement child in this.InternalChildren)

            {

                // Call Measure for each child ...

                child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

 

                // Examine DesiredSize property of child.

                this.sizeLargest.Width = Math.Max(this.sizeLargest.Width, child.DesiredSize.Width);

                this.sizeLargest.Height = Math.Max(this.sizeLargest.Height, child.DesiredSize.Height);

            }

 

            if (this.InternalChildren.Count == 1)

            {

                double diagonal = Math.Sqrt((this.sizeLargest.Width * this.sizeLargest.Width) + (this.sizeLargest.Height * this.sizeLargest.Height));

                return new Size(diagonal, diagonal);

            }

 

            double halfLargestSpan;

            double largestHeight;

            if (this.Orientation == Orientation.Horizontal)

            {

                halfLargestSpan = this.sizeLargest.Width / 2.0;

                largestHeight = this.sizeLargest.Height;

            }

            else

            {

                halfLargestSpan = this.sizeLargest.Height / 2.0;

                largestHeight = this.sizeLargest.Width;

            }

 

            // Calculate the distance from the center to element edges.

            this.innerEdgeFromCenter = (this.InternalChildren.Count == 2) ? 0.0 : halfLargestSpan / Math.Tan(this.angleEach * Math.PI / 360.0);

            this.outerEdgeFromCenter = this.innerEdgeFromCenter + largestHeight;

 

            // Calculate the radius of the circle based on the largest child.

            this.radius = Math.Sqrt((halfLargestSpan * halfLargestSpan) + (this.outerEdgeFromCenter * this.outerEdgeFromCenter));

 

            // Return the size of that circle.

            return new Size(2.0 * this.radius, 2.0 * this.radius);

        }

 

        /// <summary>

        ///   Arranges the content of a RadialPanel element.

        /// </summary>

        /// <param name="sizeFinal">

        ///   The <see cref="T:System.Windows.Size"/> that this element should use to arrange its child elements.

        /// </param>

        /// <returns>

        ///   The <see cref="T:System.Windows.Size"/> that represents the arranged size of this RadialPanel

        ///   element and its child elements.

        /// </returns>

        protected override Size ArrangeOverride(Size sizeFinal)

        {

            if (this.InternalChildren.Count == 0)

                return sizeFinal;

            if (this.InternalChildren.Count == 1)

            {

                UIElement child = this.InternalChildren[0];

                child.RenderTransform = Transform.Identity;

                Point center = new Point(

                    (sizeFinal.Width - this.sizeLargest.Width) / 2.0,

                    (sizeFinal.Height - this.sizeLargest.Height) / 2.0);

                child.Arrange(new Rect(center, new Size(this.sizeLargest.Width, this.sizeLargest.Height)));

 

                if (this.Orientation == Orientation.Vertical)

                {

                    Point rotatePoint = this.TranslatePoint(center, child);

                    rotatePoint.X += this.sizeLargest.Width / 2.0;

                    rotatePoint.Y += this.sizeLargest.Height / 2.0;

                    child.RenderTransform = new RotateTransform(-90.0, rotatePoint.X, rotatePoint.Y);

                }

 

                return sizeFinal;

            }

 

            double angleChild = (this.Orientation == Orientation.Horizontal) ? 0.0 : -90.0;

            Point centerPoint = new Point(sizeFinal.Width / 2.0, sizeFinal.Height / 2.0);

            double multiplier = Math.Min(sizeFinal.Width, sizeFinal.Height) / (2.0 * this.radius);

 

            foreach (UIElement child in this.InternalChildren)

            {

                // Reset RenderTransform.

                child.RenderTransform = Transform.Identity;

 

                if (this.Orientation == Orientation.Horizontal)

                {

                    // Position the child at the top.

                    child.Arrange(

                        new Rect(

                            centerPoint.X - (multiplier * this.sizeLargest.Width / 2.0),

                            centerPoint.Y - (multiplier * this.outerEdgeFromCenter),

                            multiplier * this.sizeLargest.Width,

                            multiplier * this.sizeLargest.Height));

                }

                else

                {

                    // Position the child at the right.

                    child.Arrange(

                        new Rect(

                            centerPoint.X + (multiplier * this.innerEdgeFromCenter),

                            centerPoint.Y - (multiplier * this.sizeLargest.Height / 2.0),

                            multiplier * this.sizeLargest.Width,

                            multiplier * this.sizeLargest.Height));

                }

 

                // Rotate the child around the center (relative to the child).

                Point pt = TranslatePoint(centerPoint, child);

                child.RenderTransform = new RotateTransform(angleChild, pt.X, pt.Y);

 

                angleChild += this.angleEach;

            }

 

            return sizeFinal;

        }

 

        /// <summary>

        ///   Draws the content of a <see cref="T:System.Windows.Media.DrawingContext"/> object during

        ///   the render pass of a RadialPanel element.

        /// </summary>

        /// <param name="dc">

        ///   The <see cref="T:System.Windows.Media.DrawingContext"/> object to draw.

        /// </param>

        protected override void OnRender(DrawingContext dc)

        {

            base.OnRender(dc);

 

            if (this.ShowPieLines)

            {

                Point centerPoint = new Point(RenderSize.Width / 2.0, RenderSize.Height / 2.0);

                double radius = Math.Min(RenderSize.Width, RenderSize.Height) / 2;

                Pen pen = new Pen(this.Foreground, 1.0);

                pen.DashStyle = DashStyles.Dash;

 

                // Display circle.

                dc.DrawEllipse(null, pen, centerPoint, radius, radius);

 

                if (this.InternalChildren.Count == 1)

                    return;

 

                // Initialize angle.

                double angleChild = -(this.angleEach / 2.0) - 90.0;

 

                // Loop through each child to draw radial lines from center.

                foreach (UIElement child in this.InternalChildren)

                {

                    double angleChildInRadian = 2.0 * Math.PI * angleChild / 360;

                    dc.DrawLine(pen, centerPoint, new Point(centerPoint.X + (radius * Math.Cos(angleChildInRadian)), centerPoint.Y + (radius * Math.Sin(angleChildInRadian))));

                    angleChild += this.angleEach;

                }

            }

        }

    }

}

Tuesday, August 12, 2008

Fujitsu T5010 Tablet PC

Got my hands on the brand new, not in the market T5010! Designed as a successor to T4220, the T5010 comes with a Centrino 2 chipset and a processor from the P family. The Centrino 2 chipset features a faster integrated graphics (Intel GMA X4500HD) and support for Wi-Fi a/g/n. The P family of processors is the newest from Intel, consuming less power than the T series of processors. Mine comes with P8600 (2.4GHz, 3MiB L2 cache, 1066MHz FSB). The top of the line processors in the P series are the P9xx, with 6MiB L2 cache, the largest found in laptops. The metal hinges also received an upgrade. It is now much more sturdy and vibrates less when in laptop mode. The harddisk comes preformatted with 2 drives. There are another 2 partitions that is named EISA Configuration which I had yet to figure out what they are for. Device manager gives a long lists spanning 4 screens...
Update: Here's the Non-Plug and Play devices
Off to install my programs. Cya!

Sunday, January 13, 2008

Placeholder text using Adorner

In the previous post, I subclassed TextBox in order to introduce a placeholder text. I realised that there is a better way - one that allows me to use only 1 class to add placeholder text to a TextBox, RichTextBox and PasswordBox - and that is using an adorner.



To use it in xaml, simply add
xmlns:src="clr-namespace:Huan.WhiteDwarf.UI"
to the root element, e.g. Window, and add
src:Placeholder.Text="Your Placeholder"
to the TextBox/RichTextBox/PasswordBox.

To use it in code, use this
Huan.WhiteDwarf.UI.Placeholder.SetText(TextBoxName, PlaceholderText);

using System;

using System.Globalization;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Controls.Primitives;

using System.Windows.Documents;

using System.Windows.Media;

 

namespace Huan.WhiteDwarf.UI

{

    /// <summary>

    ///   Represents an adorner that adds placeholder text to a <see cref="T:System.Windows.Controls.TextBox"/>,

    ///   <see cref="T:System.Windows.Controls.RichTextBox"/> or <see cref="T:System.Windows.Controls.PasswordBox"/>.

    /// </summary>

    public class Placeholder : Adorner

    {

        /// <summary>

        ///   Event handler for <see cref="E:System.Windows.Controls.Primitives.TextBoxBase.TextChanged" />.

        /// </summary>

        private readonly TextChangedEventHandler _textChangedHandler;

 

        /// <summary>

        ///   Event handler for <see cref="E:System.Windows.Controls.PasswordBox.PasswordChanged" />.

        /// </summary>

        private readonly RoutedEventHandler _passwordChangedHandler;

 

        /// <summary>

        ///   <see langword="true" /> when the placeholder text is visible, <see langword="false" /> otherwise.

        ///   Used to avoid calling <see cref="M:System.Windows.UIElement.InvalidateVisual"/> unnecessarily.

        /// </summary>

        private bool _isPlaceholderVisible;

 

        #region Dependency Property

        /// <summary>

        ///    Identifies the Huan.WhiteDwarf.UI.Placeholder.Text attached property.

        /// </summary>

        public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(

            "Text", typeof(string), typeof(Placeholder),

            new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(OnTextChanged)));

        #endregion

 

        #region Constructors

        /// <summary>

        ///   Initializes a new instance of the <see cref="T:Huan.WhiteDwarf.UI.Placeholder"/> class.

        /// </summary>

        /// <param name="adornedElement">

        ///   The element to bind the adorner to.

        /// </param>

        /// <exception cref="T:System.ArgumentNullException">

        ///   Raised when adornedElement is null.

        /// </exception>

        protected Placeholder(Control adornedElement)

            : base(adornedElement)

        {

            this.IsHitTestVisible = false;

            _textChangedHandler = new TextChangedEventHandler(AdornedElement_ContentChanged);

            _passwordChangedHandler = new RoutedEventHandler(AdornedElement_ContentChanged);

 

            adornedElement.GotFocus += new RoutedEventHandler(AdornedElement_GotFocus);

            adornedElement.LostFocus += new RoutedEventHandler(AdornedElement_LostFocus);

        }

 

        /// <summary>

        ///   Initializes a new instance of the <see cref="T:Huan.WhiteDwarf.UI.Placeholder"/> class.

        /// </summary>

        /// <param name="adornedElement">

        ///   The element to bind the adorner to.

        /// </param>

        /// <exception cref="T:System.ArgumentNullException">

        ///   Raised when adornedElement is null.

        /// </exception>

        public Placeholder(PasswordBox adornedElement)

            : this((Control)adornedElement)

        {

            if (!adornedElement.IsFocused)

                adornedElement.PasswordChanged += _passwordChangedHandler;

        }

 

        /// <summary>

        ///   Initializes a new instance of the <see cref="T:Huan.WhiteDwarf.UI.Placeholder"/> class.

        /// </summary>

        /// <param name="adornedElement">

        ///   The element to bind the adorner to.

        /// </param>

        /// <exception cref="T:System.ArgumentNullException">

        ///   Raised when adornedElement is null.

        /// </exception>

        public Placeholder(TextBox adornedElement)

            : this((Control)adornedElement)

        {

            if (!adornedElement.IsFocused)

                adornedElement.TextChanged += _textChangedHandler;

        }

 

        /// <summary>

        ///   Initializes a new instance of the <see cref="T:Huan.WhiteDwarf.UI.Placeholder"/> class.

        /// </summary>

        /// <param name="adornedElement">

        ///   The element to bind the adorner to.

        /// </param>

        /// <exception cref="T:System.ArgumentNullException">

        ///   Raised when adornedElement is null.

        /// </exception>

        public Placeholder(RichTextBox adornedElement)

            : this((Control)adornedElement)

        {

            if (!adornedElement.IsFocused)

                adornedElement.TextChanged += _textChangedHandler;

        }

        #endregion

 

        #region Property Changed Callbacks

        /// <summary>

        ///   Invoked whenever Huan.WhiteDwarf.UI.Placeholder.Text attached property is changed.

        /// </summary>

        /// <param name="sender">

        ///   The object where the event handler is attached.

        /// </param>

        /// <param name="e">

        ///   Provides data about the event.

        /// </param>

        private static void OnTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)

        {

            Control adornedElement = sender as Control;

            if (adornedElement.IsLoaded)

                AddAdorner(adornedElement);

            else

                adornedElement.Loaded += new RoutedEventHandler(AdornedElement_Loaded);

        }

        #endregion

 

        #region Event Handlers

        /// <summary>

        ///   Event handler for AdornedElement.Loaded.

        /// </summary>

        /// <param name="sender">

        ///   The AdornedElement where the event handler is attached.

        /// </param>

        /// <param name="e">

        ///   Provides data about the event.

        /// </param>

        private static void AdornedElement_Loaded(object sender, RoutedEventArgs e)

        {

            Control adornedElement = (Control)sender;

            adornedElement.Loaded -= AdornedElement_Loaded;

            AddAdorner(adornedElement);

        }

 

        /// <summary>

        ///   Event handler for AdornedElement.GotFocus.

        /// </summary>

        /// <param name="sender">

        ///   The AdornedElement where the event handler is attached.

        /// </param>

        /// <param name="e">

        ///   Provides data about the event.

        /// </param>

        private void AdornedElement_GotFocus(object sender, RoutedEventArgs e)

        {

            TextBoxBase textBoxBase = AdornedElement as TextBoxBase;

            if (textBoxBase != null)

                textBoxBase.TextChanged -= AdornedElement_ContentChanged;

            else

            {

                PasswordBox passwordBox = AdornedElement as PasswordBox;

                if (passwordBox != null)

                    passwordBox.PasswordChanged -= AdornedElement_ContentChanged;

            }

 

            if (_isPlaceholderVisible)

                this.InvalidateVisual();

        }

 

        /// <summary>

        ///   Event handler for AdornedElement.LostFocus.

        /// </summary>

        /// <param name="sender">

        ///   The AdornedElement where the event handler is attached.

        /// </param>

        /// <param name="e">

        ///   Provides data about the event.

        /// </param>

        public void AdornedElement_LostFocus(object sender, RoutedEventArgs e)

        {

            TextBoxBase textBoxBase = AdornedElement as TextBoxBase;

            if (textBoxBase != null)

                textBoxBase.TextChanged += _textChangedHandler;

            else

            {

                PasswordBox passwordBox = AdornedElement as PasswordBox;

                if (passwordBox != null)

                    passwordBox.PasswordChanged += _passwordChangedHandler;

            }

 

            if (!_isPlaceholderVisible && IsElementEmpty())

                this.InvalidateVisual();

        }

 

        /// <summary>

        ///   Event handler for AdornedElement.ContentChanged.

        /// </summary>

        /// <param name="sender">

        ///   The AdornedElement where the event handler is attached.

        /// </param>

        /// <param name="e">

        ///   Provides data about the event.

        /// </param>

        private void AdornedElement_ContentChanged(object sender, RoutedEventArgs e)

        {

            if (_isPlaceholderVisible ^ IsElementEmpty())

                this.InvalidateVisual();

        }

        #endregion

 

        #region Attached Property Getters and Setters

        /// <summary>

        ///   Gets the value of the Huan.WhiteDwarf.UI.Placeholder.Text attached property for a specified element.

        /// </summary>

        /// <param name="adornedElement">

        ///   The element from which the property value is read.

        /// </param>

        /// <returns>

        ///   The placeholder text property value for the element.

        /// </returns>

        /// <exception cref="T:ArgumentNullException">

        ///   Raised when adornedElement is null.

        /// </exception>

        public static string GetText(Control adornedElement)

        {

            if (adornedElement == null)

                throw new ArgumentNullException("adornedElement");

 

            return (string)adornedElement.GetValue(TextProperty);

        }

 

        /// <summary>

        ///   Sets the value of the Huan.WhiteDwarf.UI.Placeholder.Text attached property to a specified element.

        /// </summary>

        /// <param name="adornedElement">

        ///   The element to which the attached property is written.

        /// </param>

        /// <param name="placeholderText">

        ///   The needed placeholder text value.

        /// </param>

        /// <exception cref="T:ArgumentNullException">

        ///   Raised when adornedElement is null.

        /// </exception>

        /// <exception cref="T:InvalidOperationException">

        ///   Raised when adornedElement is not a <see cref="T:System.Windows.Controls.TextBox"/>,

        ///   <see cref="T:System.Windows.Controls.RichTextBox"/> or <see cref="T:System.Windows.Controls.PasswordBox"/>.

        /// </exception>

        public static void SetText(Control adornedElement, string placeholderText)

        {

            if (adornedElement == null)

                throw new ArgumentNullException("adornedElement");

 

            if (!(adornedElement is TextBox || adornedElement is RichTextBox || adornedElement is PasswordBox))

                throw new InvalidOperationException();

 

            adornedElement.SetValue(TextProperty, placeholderText);

        }

        #endregion

 

        /// <summary>

        ///   Adds a <see cref="T:Huan.WhiteDwarf.UI.Placeholder"/> to the adorner layer.

        /// </summary>

        /// <param name="adornedElement">

        ///   The adorned element.

        /// </param>

        private static void AddAdorner(Control adornedElement)

        {

            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);

            if (adornerLayer == null)

                return;

 

            Adorner[] adorners = adornerLayer.GetAdorners(adornedElement);

            if (adorners != null)

                foreach (Adorner adorner in adorners)

                    if (adorner is Placeholder)

                        return;

 

            TextBox textBox = adornedElement as TextBox;

            if (textBox != null)

            {

                adornerLayer.Add(new Placeholder(textBox));

                return;

            }

 

            RichTextBox richTextBox = adornedElement as RichTextBox;

            if (richTextBox != null)

            {

                adornerLayer.Add(new Placeholder(richTextBox));

                return;

            }

 

            PasswordBox passwordBox = adornedElement as PasswordBox;

            if (passwordBox != null)

            {

                adornerLayer.Add(new Placeholder(passwordBox));

                return;

            }

        }

 

        /// <summary>

        ///   Checks if the content of the adorned element is empty.

        /// </summary>

        /// <returns>

        ///   Returns <see langword="true" /> if the content is empty, <see langword="false" /> otherwise.

        /// </returns>

        private bool IsElementEmpty()

        {

            UIElement adornedElement = AdornedElement;

            TextBox textBox = adornedElement as TextBox;

            if (textBox != null)

                return string.IsNullOrEmpty(textBox.Text);

 

            PasswordBox passwordBox = adornedElement as PasswordBox;

            if (passwordBox != null)

                return string.IsNullOrEmpty(passwordBox.Password);

 

            RichTextBox richTextBox = adornedElement as RichTextBox;

            if (richTextBox != null)

            {

                BlockCollection blocks = richTextBox.Document.Blocks;

                if (blocks.Count == 0)

                    return true;

                if (blocks.Count == 1)

                {

                    Paragraph paragraph = blocks.FirstBlock as Paragraph;

                    if (paragraph == null)

                        return false;

 

                    if (paragraph.Inlines.Count == 0)

                        return true;

 

                    if (paragraph.Inlines.Count == 1)

                    {

                        Run run = paragraph.Inlines.FirstInline as Run;

                        return (run != null && string.IsNullOrEmpty(run.Text));

                    }

                }

 

                return false;

            }

 

            return false;

        }

 

        /// <summary>

        ///    Computes the text alignment of the adorned element.

        /// </summary>

        /// <returns>

        ///   Returns the computed text alignment.

        /// </returns>

        private TextAlignment ComputedTextAlignment()

        {

            Control adornedElement = AdornedElement as Control;

            TextBox textBox = adornedElement as TextBox;

            if (textBox != null)

            {

                if (DependencyPropertyHelper.GetValueSource(textBox, TextBox.HorizontalContentAlignmentProperty)

                    .BaseValueSource != BaseValueSource.Local ||

                    DependencyPropertyHelper.GetValueSource(textBox, TextBox.TextAlignmentProperty)

                    .BaseValueSource == BaseValueSource.Local)

 

                    // TextAlignment dominates

                    return textBox.TextAlignment;

            }

 

            RichTextBox richTextBox = adornedElement as RichTextBox;

            if (richTextBox != null)

            {

                BlockCollection blocks = richTextBox.Document.Blocks;

                TextAlignment textAlignment = richTextBox.Document.TextAlignment;

                if (blocks.Count == 0)

                    return textAlignment;

 

                if (blocks.Count == 1)

                {

                    Paragraph paragraph = blocks.FirstBlock as Paragraph;

                    if (paragraph == null)

                        return textAlignment;

 

                    return paragraph.TextAlignment;

                }

 

                return textAlignment;

            }

 

            switch (adornedElement.HorizontalContentAlignment)

            {

                case HorizontalAlignment.Left:

                    return TextAlignment.Left;

                case HorizontalAlignment.Right:

                    return TextAlignment.Right;

                case HorizontalAlignment.Center:

                    return TextAlignment.Center;

                case HorizontalAlignment.Stretch:

                    return TextAlignment.Justify;

            }

 

            return TextAlignment.Left;

        }

 

        /// <summary>

        ///   Draws the content of a <see cref="T:System.Windows.Media.DrawingContext" /> object during the render pass of a <see cref="T:Huan.WhiteDwarf.UI.Placeholder"/> element.

        /// </summary>

        /// <param name="drawingContext">

        ///   The <see cref="T:System.Windows.Media.DrawingContext" /> object to draw. This context is provided to the layout system.

        /// </param>

        protected override void OnRender(DrawingContext drawingContext)

        {

            Control adornedElement = this.AdornedElement as Control;

            string placeholderText;

 

            if (adornedElement == null ||

                adornedElement.IsFocused ||

                !IsElementEmpty() ||

                string.IsNullOrEmpty(placeholderText = (string)adornedElement.GetValue(TextProperty)))

 

                _isPlaceholderVisible = false;

            else

            {

                _isPlaceholderVisible = true;

                Size size = adornedElement.RenderSize;

                TextAlignment computedTextAlignment = ComputedTextAlignment();

                // foreground brush does not need to be dynamic. OnRender called when SystemColors changes.

                Brush foreground = SystemColors.GrayTextBrush.Clone();

                foreground.Opacity = adornedElement.Foreground.Opacity;

                Typeface typeface = new Typeface(adornedElement.FontFamily, FontStyles.Italic, adornedElement.FontWeight, adornedElement.FontStretch);

                FormattedText formattedText = new FormattedText(placeholderText,

                                                  CultureInfo.CurrentCulture,

                                                  adornedElement.FlowDirection,

                                                  typeface,

                                                  adornedElement.FontSize,

                                                  foreground);

                formattedText.TextAlignment = computedTextAlignment;

                formattedText.MaxTextHeight = size.Height - adornedElement.BorderThickness.Top - adornedElement.BorderThickness.Bottom - adornedElement.Padding.Top - adornedElement.Padding.Bottom;

                formattedText.MaxTextWidth = size.Width - adornedElement.BorderThickness.Left - adornedElement.BorderThickness.Right - adornedElement.Padding.Left - adornedElement.Padding.Right - 4.0;

 

                double left;

                double top = 0.0;

                if (adornedElement.FlowDirection == FlowDirection.RightToLeft)

                    left = adornedElement.BorderThickness.Right + adornedElement.Padding.Right + 2.0;

                else

                    left = adornedElement.BorderThickness.Left + adornedElement.Padding.Left + 2.0;

 

                switch (adornedElement.VerticalContentAlignment)

                {

                    case VerticalAlignment.Top:

                    case VerticalAlignment.Stretch:

                        top = adornedElement.BorderThickness.Top + adornedElement.Padding.Top;

                        break;

                    case VerticalAlignment.Bottom:

                        top = size.Height - adornedElement.BorderThickness.Bottom - adornedElement.Padding.Bottom - formattedText.Height;

                        break;

                    case VerticalAlignment.Center:

                        top = (size.Height + adornedElement.BorderThickness.Top - adornedElement.BorderThickness.Bottom + adornedElement.Padding.Top - adornedElement.Padding.Bottom - formattedText.Height) / 2.0;

                        break;

                }

 

                if (adornedElement.FlowDirection == FlowDirection.RightToLeft)

                {

                    // Somehow everything got drawn reflected. Add a transform to correct.

                    drawingContext.PushTransform(new ScaleTransform(-1.0, 1.0, RenderSize.Width / 2.0, 0.0));

                    drawingContext.DrawText(formattedText, new Point(left, top));

                    drawingContext.Pop();

                }

                else

                    drawingContext.DrawText(formattedText, new Point(left, top));

            }

        }

    }

}