#1,162 – Layout in Action, part I

Layout in WPF dictates how layout panels (containers) arrange their child elements.  Layout consists of two phases:

  • Measure – Container asks each child what its desired size is
    • Container calls Measure on each child element
    • In MeasureOverride, child element determines how much size it wants (typically by calling Measure on each of its own child elements)
    • Child element returns its desired size, to fit its child elements
  • Arrange – Container figures out how to arrange its children and decides on final position and size of each child
    • Container calls Arrange on each child element, passing in a size
    • In ArrangeOverride, child element is told how big it should be
    • Child element in turn tells each of its visual children how big they are going to be, by calling their Arrange methods
    • Child element returns final total arranged size to container

#1,161 – Using Custom Circular Progress Shape in Control Template

In the previous example, we created a custom circular progress shape and then composed it along with a TextBlock to get a labeled circular progress control.

The other approach that we could take is to just create a new control template containing our circular progress shape and a TextBlock and then use that new template as the control template for a standard ProgressBar.

Assuming that we start with the earlier code for CircularProgress, we compose the template and use it as follows:

	<Window.Resources>
        <loc:DoubleToPctConverter x:Key="dblToPct"/>
		<Style x:Key="pbarCircularStyle" TargetType="{x:Type ProgressBar}">
			<Setter Property="Foreground" Value="#01D328"/>
            <Setter Property="Maximum" Value="100"/>
            <Setter Property="Height" Value="100"/>
            <Setter Property="Width" Value="100"/>
            <Setter Property="Template">
				<Setter.Value>
					<ControlTemplate TargetType="{x:Type ProgressBar}">
						<Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
                            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
                                       FontSize="32" Foreground="DarkGray"
                                       Text="{TemplateBinding Value, Converter={StaticResource dblToPct}}" />
                            <loc:CircularProgress Stroke="{TemplateBinding Foreground}"
                                                  Value="{TemplateBinding Value}"/>
						</Grid>
					</ControlTemplate>
				</Setter.Value>
			</Setter>
		</Style>
	</Window.Resources>

    <StackPanel>
        <Grid>
			<ProgressBar Style="{DynamicResource pbarCircularStyle}"
						 Value="{Binding PctComplete}"
						 Margin="10"/>
        </Grid>
        <Button Content="Start Timer" Click="Button_Click"
                HorizontalAlignment="Center"
                Padding="12,7"/>
    </StackPanel>

1161-001

For completeness, here’s the source code for our value converter:

    public class DoubleToPctConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string result = "";
            double d = (double)value;
            result = string.Format("{0}%", d);

            return result;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

#1,160 – Adding a Text Label to the Circular Progress Control

In an earlier post, we created a custom circular progress control, deriving from a Shape element.  Below, we add a text element in the middle of the progress arc to indicate % complete.  We could have also done this by building the text into the control itself.  But the approach shown below is an example of combining the progress control with a user-defined text label.

    <StackPanel>
        <Grid>
            <loc:CircularProgress
                x:Name="circProg"
                Value="{Binding PctComplete}"
                Height="100" Width="100" Margin="5"
                HorizontalAlignment="Center"/>
            <TextBlock
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontSize="36" Foreground="DarkGray"
                Text="{Binding ElementName=circProg, Path=Value}" />
        </Grid>
        <ProgressBar x:Name="prog2" Maximum="100"
                     Value="{Binding PctComplete}"
                     Height="25" Margin="10"/>
        <Button Content="Start Timer" Click="Button_Click"
                HorizontalAlignment="Center"
                Padding="12,7"/>
    </StackPanel>

1160-001

#1,159 – Managing using Directives, part II

You can add missing using directives by using the Resolve command.  You can also clean up the current list of using directives in a file, removing the ones that are no longer needed.

You can remove unneeded using directives by clicking anywhere within a file and selecting Organize Usings | Remove Unused Usings.

In the example below, we start out with 20 using directives at the top of the file.

1159-001

We then select Remove Unused Usings.

1159-002

After we execute this command, we’re left with only four using directives at the top of the file.

1159-003

#1,158 – Managing using Directives, part I

As you write code, Visual Studio will let you know that you’ve used an identifier that it doesn’t know by marking it with a red squiggly underline.  Below, we’ve started creating a class that derives from Shape.  But Visual Studio tells us that it doesn’t know about Shape.

1158-001

The easiest way to resolve this is to try right-clicking on the unknown identifer and selecting the Resolve entry.  Visual Studio will give you a list of namespaces that it can find the identifier in.  You can then select one of these options and Visual Studio will add the relevant using directive.

1158-002

 

1158-003

Note that this will only work if the identifer is valid somewhere within the assemblies that your project has referenced.  If you have correctly spelled an identifer and the Resolve option does not appear, you will need to add a reference to the assembly where the identifier in question is defined.

 

 

#1,157 – Overridden Default Property Values Appear in Property Pane

In an earlier example of a custom control, we used the OverrideMetadata method to provide new default values for several dependency properties, as used in our control.  For example, we set the Stroke property to a green brush and StrokeThickness to 10.0.

        static CircularProgress()
        {
            Brush myGreenBrush = new SolidColorBrush(Color.FromArgb(255, 6, 176, 37));
            myGreenBrush.Freeze();

            StrokeProperty.OverrideMetadata(
                typeof(CircularProgress),
                new FrameworkPropertyMetadata(myGreenBrush));
            FillProperty.OverrideMetadata(
                typeof(CircularProgress),
                new FrameworkPropertyMetadata(Brushes.Transparent));

            StrokeThicknessProperty.OverrideMetadata(
                typeof(CircularProgress),
                new FrameworkPropertyMetadata(10.0));
        }

These new values will show up as expected in the property pane within Visual Studio. Below, we can see both the green brush for Stroke and the value of 10.0 for StrokeThickness.

1157-001

#1,156 – Changing Circular Progress Control to Be Only an Arc

An earlier post presented a circular progress control that displayed a pie-shaped filled area indicating progress.

1155-001

We can change the control to draw just the outer part of the circle (an arc) as a progress indicator.  We make the following changes:

  • Do not draw line to origin at end of shape
  • Set isClosed parameter in BeginFigure to false
  • Default Fill property to be transparent
  • Default Stroke to have thickness of 10.0

Here is the updated code:

    public class CircularProgress : Shape
    {
        static CircularProgress()
        {
            Brush myGreenBrush = new SolidColorBrush(Color.FromArgb(255, 6, 176, 37));
            myGreenBrush.Freeze();

            StrokeProperty.OverrideMetadata(
                typeof(CircularProgress),
                new FrameworkPropertyMetadata(myGreenBrush));
            FillProperty.OverrideMetadata(
                typeof(CircularProgress),
                new FrameworkPropertyMetadata(Brushes.Transparent));

            StrokeThicknessProperty.OverrideMetadata(
                typeof(CircularProgress),
                new FrameworkPropertyMetadata(10.0));
        }

        // Value (0-100)
        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        // DependencyProperty - Value (0 - 100)
        private static FrameworkPropertyMetadata valueMetadata =
                new FrameworkPropertyMetadata(
                    0.0,     // Default value
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    null,    // Property changed callback
                    new CoerceValueCallback(CoerceValue));   // Coerce value callback

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(CircularProgress), valueMetadata);

        private static object CoerceValue(DependencyObject depObj, object baseVal)
        {
            double val = (double)baseVal;
            val = Math.Min(val, 99.999);
            val = Math.Max(val, 0.0);
            return val;
        }

        protected override Geometry DefiningGeometry
        {
            get
            {
                double startAngle = 90.0;
                double endAngle = 90.0 - ((Value / 100.0) * 360.0);

                double maxWidth = Math.Max(0.0, RenderSize.Width - StrokeThickness);
                double maxHeight = Math.Max(0.0, RenderSize.Height - StrokeThickness);

                double xStart = maxWidth / 2.0 * Math.Cos(startAngle * Math.PI / 180.0);
                double yStart = maxHeight / 2.0 * Math.Sin(startAngle * Math.PI / 180.0);

                double xEnd = maxWidth / 2.0 * Math.Cos(endAngle * Math.PI / 180.0);
                double yEnd = maxHeight / 2.0 * Math.Sin(endAngle * Math.PI / 180.0);

                StreamGeometry geom = new StreamGeometry();
                using (StreamGeometryContext ctx = geom.Open())
                {
                    ctx.BeginFigure(
                        new Point((RenderSize.Width / 2.0) + xStart,
                                  (RenderSize.Height / 2.0) - yStart),
                        true,   // Filled
                        false);  // Closed
                    ctx.ArcTo(
                        new Point((RenderSize.Width / 2.0) + xEnd,
                                  (RenderSize.Height / 2.0) - yEnd),
                        new Size(maxWidth / 2.0, maxHeight / 2),
                        0.0,     // rotationAngle
                        (startAngle - endAngle) > 180,   // greater than 180 deg?
                        SweepDirection.Clockwise,
                        true,    // isStroked
                        false);
                //    ctx.LineTo(new Point((RenderSize.Width / 2.0), (RenderSize.Height / 2.0)), true, true);
                }

                return geom;
            }
        }
    }

Below is a screenshot showing the updated arc-based circular progress indicator.

1156-001