WPFSpark v1.1 released!

WPFSpark v1.1 is now released!

WPFSpark v1.1 brings a major revamp of the FluidWrapPanel control.


The core logic of FluidWrapPanel class has been rewritten from scratch to make it more robust and usable in various scenarios, resulting in a faster, optimized code.
These changes are breaking changes, meaning if you are using the latest WPFSpark library (v1.1) then your old code using the old FluidWrapPanel will not compile unless you update it. The interface IFluidDrag has been removed. Child elements no longer need to implement the IFluidDrag interface to participate in the drag and drop interaction. Instead I have added a new Behavior called FluidMouseDragBehavior which would facilitate the child element with drag and drop interaction. Child elements must add this behavior to participate in drag and drop.

Get the latest WPFSpark source code here.

WPFSpark v1.0 released

Ok, I admit, this post should have been released a few weeks ago, but last few weeks had been pretty hectic and I was unable to post about my pet project WPFSpark which reached the v1.0 milestone last month. Yes, WPFSpark  v1.0  is now released !

I have revamped the CodePlex page to give it a new look. You can access it here.

This release adds four new controls - SparkWindowFluidPivotPanelFluidProgressBar andFluidStatusBar.

It also brings the following improvements to the existing three controls:

  • SprocketControl
    • Internal timer stopped when control is no longer visible. It is started only when the control is Visible. This reduces CPU load.
    • Added the LowestAlpha dependency property which indicates the lowest Opacity value that must be used while rendering the SprocketControl’s spokes.
    • Added the AlphaTicksPercentage dependency property which indicates the percentage of total ticks which must be considered for step by step reduction of the alpha value. The remaining ticks remain at the LowestAlpha value.
    • SprocketControl now implements IDisposable.
  • ToggleSwitch
    • Added the IsCheckedLeft dependency property which indicates whether the checked content appears in the left or right side of the ToggleSwitch
    • Added the CheckedToolTip property which is displayed when the ToggleSwitch is in the Checked state. Set this property to String.Empty( “” ) to prevent this tooltip from displaying.
    • Added the UncheckedToolTip property which is displayed when the ToggleSwitch is in the Unchecked state. Set this property to String.Empty( “” ) to prevent this tooltip from displaying.
  • FluidWrapPanel
    • Added the ItemSource dependency property which can be bound to an ObservableCollection<UIElement>.

Also, I have published articles on CodeProject detailing about the newly added controls . Do check them out. The links are available at the CodePlex site.

WPFSpark : 3 of n : FluidWrapPanel

My third article in the WPFSpark series has been published at the CodeProject site.

You can view the article here.

WPFSpark is now available at the NuGet gallery. Get the WPFSpark Nuget package.

Get Notified when child is added to a Custom Panel via XAML

As I was creating the next control for my WPFSpark project – the FluidWrapPanel, a thought occurred to me. How will the FluidWrapPanel be notified when controls are added as its children via XAML?

As of now, FluidWrapPanel had an API called AddChild which would take a UIElement as a parameter. The reason behind this was that the FluidWrapPanel needs to perform some calculations before adding it to the InternalChildren. The UIElementCollection does not have a CollectionChanged to which we can subscribe to. Search over the internet yielded partial solutions to the problem. This MSDN link provided the initial starting point for my solution.

First, I defined an interface INotifiableParent which must be implemented by the Custom Panel requiring UIElementCollection change notification.

namespace WPFSpark
{
    public interface INotifiableParent
    {
        int AddChild(UIElement child);
        void RemoveChild(UIElement child);
    }
}

Next, I derived a class NotifiableUIElementCollection from the UIElementCollection class and provided a constructor, overriden methods (Add and Remove) and private member variable of type INotifiableParent.

namespace WPFSpark
{
    public class NotifiableUIElementCollection : UIElementCollection
    {
        private INotifiableParent parent;

        public NotifiableUIElementCollection(UIElement visualParent, FrameworkElement logicalParent)
            : base(visualParent, logicalParent)
        {
            parent = (INotifiableParent)logicalParent;
        }

        public override int Add(System.Windows.UIElement element)
        {
            if (parent != null)
                return parent.AddChild(element);

            return -1;
        }

        public override void Remove(UIElement element)
        {
            if (parent != null)
                parent.RemoveChild(element);
        }
    }
}

The NotifiableUIElementCollection class does not store the children within itself. Instead it delegates the work to the INotifiableParent member.

Now, the following must be done in the Custom Panel code -

  • Add a member variable of type NotifiableUIElementCollection (this member must be initialized in the constructor!)
  • Add A read-only property of type NotifiableUIElementCollection which returns the private member.
  • The ContentPropertyAttribute must be added to the CustomPanel class with the read-only Property name as the argument.
  • Implement the INotifiableParent interface.

The ContentPropertyAttribute indicates which property of a type is the XAML content property.

Important: In the implementation of the AddChild method, you must add the child to the Children property of the CustomPanel otherwise it will not be rendered on the panel.

Here in the sample panel below, I am expecting that TextBoxes will be added to it via XAML and I am assigning a value to the Text property of the TextBlock.

namespace CustomPanelSample
{
    [ContentProperty("NotifiableChildren")]
    class CustomPanel : Canvas, INotifiableParent
    {
        int count = 1;

        private NotifiableUIElementCollection notifiableChildren;

        public NotifiableUIElementCollection NotifiableChildren
        {
            get
            {
                return notifiableChildren;
            }
        }

        public CustomPanel()
        {
	    // Initialize the NotifiableUIElementCollection
            notifiableChildren = new NotifiableUIElementCollection(this, this);
        }

        #region INotifiableParent Members

        public int AddChild(System.Windows.UIElement child)
        {
            // Add your custom code here
            TextBlock tb = child as TextBlock;

            if (tb != null)
            {
                tb.Text = count++.ToString();
            }

	    // Add the child to the InternalChildren
            return this.Children.Add(child);
        }

        public void RemoveChild(System.Windows.UIElement child)
        {
            this.Children.Remove(child);
        }

        #endregion
    }
}

Here is how the CustomPanel will be used

<Window x:Class="CustomPanelSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="350"
        Width="525"
        xmlns:my="clr-namespace:CustomPanelSample">
    <Grid>
        <my:CustomPanel HorizontalAlignment="Stretch"
                        Margin="15"
                        x:Name="customPanel1"
                        VerticalAlignment="Stretch">
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="10"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="50"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="90"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="130"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="170"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="210"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="250"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
        </my:CustomPanel>
    </Grid>
</Window>

Upon executing this, the following output is shown.

In the NotifiableUIElementCollection, you can also implement the remaining overridable methods available in the UIElementCollection. In that case you must also expand the INotifiableParent interface to accommodate those methods.

Hope this solution comes handy!

Happy coding! :)

WPFSpark : 2 of n : ToggleSwitch

My second article in the WPFSpark series is out. I have added a new control called ToggleSwitch to the WPFSpark library.

You can access it here.

I’m currently finalizing the next control for the WPFSpark library. It is called FluidWrapPanel. You can catch a glimpse of it in my codeplex site.

ClipBorder: A WPF Border that clips

While creating the next control for my WPFSpark project, I had the requirement of a WPF Grid with rounded corners. Since the Grid does not have the CornerRadius property, the other option I had was to encapsulate the Grid within a Border and set the ClipToBounds property of the Border to true. Then I found out that Border (and all decorators) do not perform the clipping even though the ClipToBounds is set to true.

The MSDN Forum provided an excellent solution on how to derive from the Border class and implement the clipping on your own. The ClippingBorder class mentioned in this site has a  _clipRect field (of type RectangleGeometry) which is set as the Clip property of the ClippingBorder’s Child. But in this case the corner radius of the RectangleGeometry is same for all corners. What if you have a border with different corner radius for each corner and you want it to act as a clipping border?

I modified the ClippingBorder class to create the ClipBorder class which will take into account the above mentioned issue. Instead of a RectangleGeometry field, the ClipBorder class uses a PathGeometry to define the clipping Geometry. For this purpose I have used the GeometryHelper class (mentioned in my previous post) to obtain the RoundedRectangleGeometry. I have modified the GeometryHelper code further to take into account the BorderThickness of the ClipBorder.

Here is the code for ClipBorder:

namespace WPFSpark
{
    /// <summary>
    /// Border which allows Clipping to its border.
    /// Useful especially when you need to clip to round corners.
    /// </summary>
    public class ClipBorder : Border
    {
        protected override void OnRender(DrawingContext dc)
        {
            OnApplyChildClip();
            base.OnRender(dc);
        }

        public override UIElement Child
        {
            get
            {
                return base.Child;
            }
            set
            {
                if (this.Child != value)
                {
                    if (this.Child != null)
                    {
                        // Restore original clipping of the old child
                        this.Child.SetValue(UIElement.ClipProperty, oldClip);
                    }

                    if (value != null)
                    {
                        // Store the current clipping of the new child
                        oldClip = value.ReadLocalValue(UIElement.ClipProperty);
                    }
                    else
                    {
                        // If we dont set it to null we could leak a Geometry object
                        oldClip = null;
                    }

                    base.Child = value;
                }
            }
        }

        protected virtual void OnApplyChildClip()
        {
            UIElement child = this.Child;
            if (child != null)
            {
                // Get the geometry of a rounded rectangle border based on the BorderThickness and CornerRadius
                clipGeometry = GeometryHelper.GetRoundRectangle(new Rect(Child.RenderSize), this.BorderThickness, this.CornerRadius);
                child.Clip = clipGeometry;
            }
        }

        private Geometry clipGeometry = null;
        private object oldClip;
    }
}

Update: There was a small error in the calculation of the RoundedRectangle Geometry when the BorderThickness was 1 pixel. I have rectified it and updated the code. Thanks to Gene for pointing it out. :)

Here is the updated code for the modified GeometryHelper:

public static Geometry GetRoundRectangle(Rect baseRect, Thickness thickness, CornerRadius cornerRadius)
{
    // Normalizing the corner radius
    if (cornerRadius.TopLeft < Double.Epsilon)
        cornerRadius.TopLeft = 0.0;
    if (cornerRadius.TopRight < Double.Epsilon)
        cornerRadius.TopRight = 0.0;
    if (cornerRadius.BottomLeft < Double.Epsilon)
        cornerRadius.BottomLeft = 0.0;
    if (cornerRadius.BottomRight < Double.Epsilon)
        cornerRadius.BottomRight = 0.0;

    // Taking the border thickness into account
    double leftHalf = thickness.Left * 0.5;
    if (leftHalf < Double.Epsilon)
        leftHalf = 0.0;
    double topHalf = thickness.Top * 0.5;
    if (topHalf < Double.Epsilon)
        topHalf = 0.0;
    double rightHalf = thickness.Right * 0.5;
    if (rightHalf < Double.Epsilon)
        rightHalf = 0.0;
    double bottomHalf = thickness.Bottom * 0.5;
    if (bottomHalf < Double.Epsilon) 
        bottomHalf = 0.0;

    // Create the rectangles for the corners that needs to be curved in the base rectangle 
    // TopLeft Rectangle 
    Rect topLeftRect = new Rect(baseRect.Location.X, 
                                baseRect.Location.Y, 
                                Math.Max(0.0, cornerRadius.TopLeft - leftHalf), 
                                Math.Max(0.0, cornerRadius.TopLeft - rightHalf)); 
    // TopRight Rectangle 
    Rect topRightRect = new Rect(baseRect.Location.X + baseRect.Width - cornerRadius.TopRight + rightHalf, 
                                 baseRect.Location.Y, 
                                 Math.Max(0.0, cornerRadius.TopRight - rightHalf), 
                                 Math.Max(0.0, cornerRadius.TopRight - topHalf));   
    // BottomRight Rectangle
    Rect bottomRightRect = new Rect(baseRect.Location.X + baseRect.Width - cornerRadius.BottomRight + rightHalf, 
                                    baseRect.Location.Y + baseRect.Height - cornerRadius.BottomRight + bottomHalf, 
                                    Math.Max(0.0, cornerRadius.BottomRight - rightHalf), 
                                    Math.Max(0.0, cornerRadius.BottomRight - bottomHalf)); 
    // BottomLeft Rectangle 
    Rect bottomLeftRect = new Rect(baseRect.Location.X, 
                                   baseRect.Location.Y + baseRect.Height - cornerRadius.BottomLeft + bottomHalf, 
                                   Math.Max(0.0, cornerRadius.BottomLeft - leftHalf),  
                                   Math.Max(0.0, cornerRadius.BottomLeft - bottomHalf)); 

    // Adjust the width of the TopLeft and TopRight rectangles so that they are proportional to the width of the baseRect 
    if (topLeftRect.Right > topRightRect.Left)
    {
        double newWidth = (topLeftRect.Width / (topLeftRect.Width + topRightRect.Width)) * baseRect.Width;
        topLeftRect = new Rect(topLeftRect.Location.X, topLeftRect.Location.Y, newWidth, topLeftRect.Height);
        topRightRect = new Rect(baseRect.Left + newWidth, topRightRect.Location.Y, Math.Max(0.0, baseRect.Width - newWidth), topRightRect.Height);
    }

    // Adjust the height of the TopRight and BottomRight rectangles so that they are proportional to the height of the baseRect
    if (topRightRect.Bottom > bottomRightRect.Top)
    {
        double newHeight = (topRightRect.Height / (topRightRect.Height + bottomRightRect.Height)) * baseRect.Height;
        topRightRect = new Rect(topRightRect.Location.X, topRightRect.Location.Y, topRightRect.Width, newHeight);
        bottomRightRect = new Rect(bottomRightRect.Location.X, baseRect.Top + newHeight, bottomRightRect.Width, Math.Max(0.0, baseRect.Height - newHeight));
    }

    // Adjust the width of the BottomLeft and BottomRight rectangles so that they are proportional to the width of the baseRect
    if (bottomRightRect.Left < bottomLeftRect.Right)
    {
        double newWidth = (bottomLeftRect.Width / (bottomLeftRect.Width + bottomRightRect.Width)) * baseRect.Width;
        bottomLeftRect = new Rect(bottomLeftRect.Location.X, bottomLeftRect.Location.Y, newWidth, bottomLeftRect.Height);
        bottomRightRect = new Rect(baseRect.Left + newWidth, bottomRightRect.Location.Y, Math.Max(0.0, baseRect.Width - newWidth), bottomRightRect.Height);
    }

    // Adjust the height of the TopLeft and BottomLeft rectangles so that they are proportional to the height of the baseRect
    if (bottomLeftRect.Top < topLeftRect.Bottom)
    {
        double newHeight = (topLeftRect.Height / (topLeftRect.Height + bottomLeftRect.Height)) * baseRect.Height;
        topLeftRect = new Rect(topLeftRect.Location.X, topLeftRect.Location.Y, topLeftRect.Width, newHeight);
        bottomLeftRect = new Rect(bottomLeftRect.Location.X, baseRect.Top + newHeight, bottomLeftRect.Width, Math.Max(0.0, baseRect.Height - newHeight));
    }

    StreamGeometry roundedRectGeometry = new StreamGeometry();

    using (StreamGeometryContext context = roundedRectGeometry.Open())
    {
        // Begin from the Bottom of the TopLeft Arc and proceed clockwise
        context.BeginFigure(topLeftRect.BottomLeft, true, true);
        // TopLeft Arc
        context.ArcTo(topLeftRect.TopRight, topLeftRect.Size, 0, false, SweepDirection.Clockwise, true, true);
        // Top Line
        context.LineTo(topRightRect.TopLeft, true, true);
        // TopRight Arc
        context.ArcTo(topRightRect.BottomRight, topRightRect.Size, 0, false, SweepDirection.Clockwise, true, true);
        // Right Line
        context.LineTo(bottomRightRect.TopRight, true, true);
        // BottomRight Arc
        context.ArcTo(bottomRightRect.BottomLeft, bottomRightRect.Size, 0, false, SweepDirection.Clockwise, true, true);
        // Bottom Line
        context.LineTo(bottomLeftRect.BottomRight, true, true);
        // BottomLeft Arc
        context.ArcTo(bottomLeftRect.TopLeft, bottomLeftRect.Size, 0, false, SweepDirection.Clockwise, true, true);
    }

    return roundedRectGeometry;
}

WPFSpark: 1 of n: SprocketControl

I revamped my WPFSpark project in codeplex and released WPFSpark v0.5 on May 28th, 2011. Check out my codeproject article describing the first control in the WPFSpark project: SprocketControl.

The article can be found here.

The source code is available here.

WPFSpark Reloaded!

It has been more than a year since I posted anything on this blog! Well the last one year was pretty eventful due to which I was unable to focus my attention towards this blog. Till June last year I was working in a Windows Forms project. I was learning WPF whenever I could get time and I was liking it very much. WPF is a powerful framework indeed. It gives wings to your imagination and creativity.
From July 2010 onwards, I shifted to a project which was working on Silverlight 4. Since then it has been a roller-coaster ride of the learning curve of Silverlight, dot NET 4 (and WPF too!). Being familiar with WPF gave me an added advantage. Though I was unhappy to discover that Silverlight was a subset of the WPF framework targeting the Web mainly, I am hopeful that one day Microsoft will merge to two to provide a robust framework which will cater both the desktop and web worlds.
In the course of learning and development in WPF/Silverlight, I encountered several problems. The search for their solutions led me to popular technical sites like CodeProject, StackOverflow. Here, tips from the gurus of WPF/Silverlight helped me widen my horizon of understanding and showed me that WPF/Silverlight is capable of far more than I originally thought.
So I have decided to share the knowledge with the developer community which has helped me so much.
As a first step, I revamped my blog. I chose a new theme to give it a cleaner look.
I will be adding more posts in the days to come…

Follow

Get every new post delivered to your Inbox.