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;
}

Handling the CornerRadius for a RoundedRectangle Geometry in WPF

Recently I was pondering on how to create a RoundedRectangle Geometry in WPF. WPF does provide a RectangleGeometry class which has the RadiusX and RadiusY properties which allow you to curve the RectangleGeometry at the corners.  Each of the corners have the same curvature. But what if you need a different corner radius at each of the corners?

If you look closely at a border with rounded corners, it can be split into 4 straight lines and 4 curves.

Thus to create the RoundedRectangleGeometry you require four LineSegments and four ArcSegments. To determine the end points of the Arcs you need to calculate the rectangles that enclose them at each corner. The LineSegments will be drawn between the EndPoint of an ArcSegment and the StartPoint of the next ArcSegment.

The next question that comes to mind is what happens if the rectangles enclosing the ArcSegments overlap (i.e. the corner radius is larger than half of the width or height of the Rounded Rectangle). The sample program which I created to test it gave me the following output when the TopLeft and TopRight corner radii were large.

Then I looked into how the Border class handles this situation and I found out that in such scenarios the width and height of the corner rectangles were recalculated in such a way that they were in proportion to each other with respect with to the width and height of the base Rounded Rectangle.

Here is the code for the class GeometryHelper which generates a PathGeometry for a RoundedRectangle based on the given BaseRect size and CornerRadius.

namespace WPFSpark
{
    public static class GeometryHelper
    {
        public static Geometry GetRoundRectangleGeometry(Rect baseRect, CornerRadius cornerRadius)
        {
            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;                          

            // 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,
                                        cornerRadius.TopLeft,
                                        cornerRadius.TopLeft);
            // TopRight Rectangle
            Rect topRightRect = new Rect(baseRect.Location.X + baseRect.Width - cornerRadius.TopRight,
                                         baseRect.Location.Y,
                                         cornerRadius.TopRight,
                                         cornerRadius.TopRight);
            // BottomRight Rectangle
            Rect bottomRightRect = new Rect(baseRect.Location.X + baseRect.Width - cornerRadius.BottomRight,
                                            baseRect.Location.Y + baseRect.Height - cornerRadius.BottomRight,
                                            cornerRadius.BottomRight,
                                            cornerRadius.BottomRight);
            // BottomLeft Rectangle
            Rect bottomLeftRect = new Rect(baseRect.Location.X,
                                           baseRect.Location.Y + baseRect.Height - cornerRadius.BottomLeft,
                                           cornerRadius.BottomLeft,
                                           cornerRadius.BottomLeft);             

            // 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);

                context.Close();
            }

            return roundedRectGeometry;
        }
    }
}

A Simple WP7 JumpList in WPF

Check out my latest article in codeproject on creating a Windows Phone 7 style JumpList in WPF.

You can view the article here.

Follow

Get every new post delivered to your Inbox.