Implementation of the WPF Dashboard control, wpfdashboard

Source: Internet
Author: User

Implementation of the WPF Dashboard control, wpfdashboard

1. Determine the base class that the control should inherit. At present, none of the commonly used controls in WPF are close to the dial control, but the control can be split and discovered, each sub-part of the Control exists in WPF. Therefore, we need to combine the sub-controls to form the dial Control. Therefore, we define a Dashboard class and inherit from the Control class. 2. Set the Dashboard Style
<Style TargetType="{x:Type local:Dashboard}"><Setter Property="BorderBrush" Value="Black" /><Setter Property="BorderThickness" Value="1" /><Setter Property="Background" Value="Transparent" /><Setter Property="SnapsToDevicePixels" Value="True" /><Setter Property="UseLayoutRounding" Value="True" /><Setter Property="HorizontalContentAlignment" Value="Left" /><Setter Property="VerticalContentAlignment" Value="Center" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type local:Dashboard}"><Grid></Grid></ControlTemplate></Setter.Value></Setter></Style>

It is important to note that because we do not know what is inside the Dashboard, a Grid is placed here, and all the subsequent code will be written in <Grid> </Grid>.

3. Determine the internal structure of the control

The dial control is composed of three parts.

  • Scale displayed in text
  • Arc with progress (ARC in red and gray)
  • Content display area in the lower middle
After determining the basic internal composition, you will find a problem. WPF does not seem to have a control such as the scale that can display text, nor is there such a control described in ②. Obviously, ① and ② are also composed of child controls. Although there is no such control in WPF, we know that there is a control such as PathListBox and Arc. Arc should know that it is an Arc, but PathListBox may be a bit unfamiliar, the following section describes a PathListBox before the Dashboard control. 3.1. PathListBox indicates the Path from the control's literal description, while ListBox indicates a list. Then, PathListBox is combined to display each Item in the list according to a certain path. The following code will show you the control ①. The namespace is in the default System of WPF. windows. under Controls, PathListBox cannot be found. You need to introduce Microsoft. expression. controls. dll, and then define the namespace alias in xaml
xmlns:ec="http://schemas.microsoft.com/expression/2010/controls"

② Usage

Since each Item is arranged according to a certain Path, a Path must be defined first. Here, a straight line Path with a length of 500 is defined first.
<Path x:Name="path" Data="M0,0 500,0" Stroke="Black" StrokeThickness="1" />

Put PathListBox, set the path to which the PathListBox should be arranged in the LayoutPath of PathListBox, set the effect of each Item in ItemsTemplate, and set the ItemsSource of PathListBox in the background, there are several Item subitems in PathListBox. The complete code is as follows:

<Grid VerticalAlignment="Center"><Path x:Name="path" Data="M0,0 500,0" Stroke="Black" StrokeThickness="1" /><ec:PathListBox x:Name="pathListBox"><ec:PathListBox.ItemTemplate><DataTemplate><Border Width="3" Height="10" Background="Black" SnapsToDevicePixels="True"UseLayoutRounding="True" /></DataTemplate></ec:PathListBox.ItemTemplate><ec:PathListBox.LayoutPaths><ec:LayoutPath Distribution="Even" Orientation="OrientToPath"   SourceElement="{Binding ElementName=path}" /></ec:PathListBox.LayoutPaths></ec:PathListBox></Grid>

Distributeion and Orientation are key attributes, and SourceElement points to the path of PathListBox. Shows the final effect:

3.2. ArcArc refers to the ARC. This control is relatively simple. You can directly paste code ①. The naming control must introduce Microsoft. Expression. Drawing. dll, and then define the namespace alias in xaml.
xmlns:ec="http://schemas.microsoft.com/expression/2010/controls"

② Usage

<ed:Arc x:Name="DoubleCircle" ArcThickness="8" ArcThicknessUnit="Pixel"EndAngle="120" StartAngle="-120" Width="200" Height="200" Fill="Red"Stretch="None" Stroke="Yellow" StrokeThickness="1" />

The key attributes are described as follows:

After learning how to use PathListBox and Arc, the following describes how to create a Dashboard. 4. formally build controls4.1. The Dashboard dial control has two kinds of scales. One is a longer scale and the other is a shorter one. If we only use a PathListBox, this effect cannot be achieved. Therefore, two pathlistboxes are used. One PathListBox is used to place a shorter scale, and the other PathListBox is used to place a longer scale, this effect can be achieved by combining the two of them. 4.1.1. the Long Scale part defines an arc from-120 ° to 120 ° and a PathListBox
<! -- Complete Arc of the dial --> <ed: Arc x: name = "LongTickPath" Margin = "0" ArcThickness = "0" ArcThicknessUnit = "Pixel" EndAngle = "120" StartAngle = "-120" Stretch = "None" Stroke = "Black "StrokeThickness =" 1 "/> <! -- Long Scale --> <ec: PathListBox x: Name = "LongTick" IsHitTestVisible = "False"> <ec: PathListBox. itemTemplate> <DataTemplate> <Border Width = "1" Height = "13" Background = "Black" SnapsToDevicePixels = "True" UseLayoutRounding = "False"/> </DataTemplate> </ ec: pathListBox. itemTemplate> <ec: PathListBox. layoutPaths> <ec: LayoutPath Distribution = "Even" Orientation = "OrientToPath" SourceElement = "{Binding ElementName = LongTickPath}"/> </ec: PathListBox. layoutPaths> </ec: PathListBox>

However, only the arc can be seen in this way, and the scale effect of PathListBox is not displayed, because ItemsSource is not set in PathListBox. In addition, because we are using a custom control, to set the ItemsSource value of PathListBox, we need to define a dependency attribute LongTicksInternal in Dashboard, because we do not want users to be able to set the LongTicksInternal value outside, when setting the dependency attribute, set its access permission to private, in this way, you can only access this dependency attribute in the style, but you cannot see this dependency attribute when using it outside.

# Region LongTicksInternal long scale set public IList <object> LongTicksInternal {get {return (IList <object>) GetValue (LongTicksInternalProperty);} private set {SetValue (LongTicksInternalProperty, value );}} public static readonly DependencyProperty LongTicksInternalProperty = DependencyProperty. register ("LongTicksInternal", typeof (IList <object>), typeof (Dashboard); # endregion

After the Dependency Property is defined, bind the dependency property to the ItemsSource bound to the PathListBox

ItemsSource="{TemplateBinding ShortTicks}"

After the dependency attribute is bound, it cannot be displayed because LongTicksInternal is a null set and LongTicksInternal must be assigned.

public Dashboard(){this.LongTicksInternal = new List<object>();for (int i = 0; i < 10; i++){this.LongTicksInternal.Add(i);}}

The effect is as follows:

# Region LongTickCount Long Scale count public int LongTickCount {get {return (int) GetValue (LongTickCountProperty);} set {SetValue (LongTickCountProperty, value );}} public static readonly DependencyProperty LongTickCountProperty = DependencyProperty. register ("LongTickCount", typeof (int), typeof (Dashboard), new PropertyMetadata (5); # endregion

Modify the for loop code above to set the number of long scales flexibly.

for (int i = 0; i < this.LongTickCount; i++){this.LongTicksInternal.Add(i);}

4.1.2. Short Scale

Define a Path and a PathListBox again, and add a dependency attribute to set the ItemsSource of PathListBox.

 

<! -- Complete Arc of the dial --> <ed: Arc x: name = "ShortTickPath" Margin = "0" ArcThickness = "0" ArcThicknessUnit = "Pixel" EndAngle = "120" StartAngle = "-120" Stretch = "None" Stroke = "Black "StrokeThickness =" 1 "/> <! -- Long Scale --> <ec: PathListBox x: Name = "ShortTick" IsHitTestVisible = "False" ItemsSource = "{TemplateBinding ShortTicksInternal}"> <ec: PathListBox. itemTemplate> <DataTemplate> <Border Width = "1" Height = "8" Background = "Black" SnapsToDevicePixels = "True" UseLayoutRounding = "False"/> </DataTemplate> </ ec: pathListBox. itemTemplate> <ec: PathListBox. layoutPaths> <ec: LayoutPath Distribution = "Even" Orientation = "OrientToPath" SourceElement = "{Binding ElementName = ShortTickPath}"/> </ec: PathListBox. layoutPaths> </ec: PathListBox>

Dependency attribute of the number of short scales

# Region ShortTicksInternal short scale set public IList <object> ShortTicksInternal {get {return (IList <object>) GetValue (ShortTicksInternalProperty);} set {SetValue (ShortTicksInternalProperty, value );}} public static readonly DependencyProperty ShortTicksInternalProperty = DependencyProperty. register ("ShortTicksInternal", typeof (IList <object>), typeof (Dashboard); # endregion

However, because there are many short scales, it is impossible to count the number of short scales in the dial. If you manually set the number of all short scales, the problem is that the short scale and Long Scale do not overlap, this causes the width to be narrow. We do not know the number of all short scales, but we can know the number of short scales between two long scales, So we define a ShortTickCount, used to set the number of short scales between two long scales

# Region ShortTickCount short scale count public int ShortTickCount {get {return (int) GetValue (ShortTickCountProperty);} set {SetValue (ShortTickCountProperty, value );}} public static readonly DependencyProperty ShortTickCountProperty = DependencyProperty. register ("ShortTickCount", typeof (int), typeof (Dashboard), new PropertyMetadata (5); # endregion

Generate ShortTicksInternal Based on LongTickCount and ShortTickCount

this.ShortTicksInternal = new List<object>();for (int i = 0; i < (this.LongTickCount - 1) * (this.ShortTickCount + 1) + 1; i++){this.ShortTicksInternal.Add(new object());}

 

Here is a brief introduction to this algorithm: LongTickCount has nine and ShortTickCount has five. As shown in the figure, we can divide the dial scale into eight parts, each part is composed of one long scale and five short scales. Therefore, the expression of each part is [ShortTickCount + 1], which is divided into eight parts in total, the expression is (LongTickCount-1) * (ShortTickCount + 1). Finally, we find that there is only one long scale for the first part, which is actually a short scale, the final expression is (LongTickCount-1) * (ShortTickCount + 1) + 1

Fine-tune the Arc border, remove it, then adjust the Margin of the Arc with a short scale, and adjust it to the bottom level of the Long Scale.
<! -- Complete Arc of the dial --> <ed: Arc x: name = "LongTickPath" Margin = "0" ArcThickness = "0" ArcThicknessUnit = "Pixel" EndAngle = "120" StartAngle = "-120" Stretch = "None" Stroke = "Black "StrokeThickness =" 0 "/> <! -- Long Scale --> <ec: PathListBox x: Name = "LongTick" IsHitTestVisible = "False" ItemsSource = "{TemplateBinding LongTicksInternal}"> <ec: PathListBox. itemTemplate> <DataTemplate> <Border Width = "1" Height = "13" Background = "Black" verticalignment = "Bottom" SnapsToDevicePixels = "True" UseLayoutRounding = "False"/> </DataTemplate> </ec: pathListBox. itemTemplate> <ec: PathListBox. layoutPaths> <ec: LayoutPath Distribution = "Even" Ori Entation = "OrientToPath" SourceElement = "{Binding ElementName = LongTickPath}"/> </ec: PathListBox. LayoutPaths> </ec: PathListBox> <! -- Complete Arc of the dial --> <ed: Arc x: name = "ShortTickPath" Margin = "5" ArcThickness = "0" ArcThicknessUnit = "Pixel" EndAngle = "120" StartAngle = "-120" Stretch = "None" Stroke = "Black "StrokeThickness =" 0 "/> <! -- Long Scale --> <ec: PathListBox x: Name = "ShortTick" IsHitTestVisible = "False" ItemsSource = "{TemplateBinding ShortTicksInternal}"> <ec: PathListBox. itemTemplate> <DataTemplate> <Border Width = "1" Height = "8" Background = "Black" verticalignment = "Bottom" SnapsToDevicePixels = "True" UseLayoutRounding = "False"/> </DataTemplate> </ec: pathListBox. itemTemplate> <ec: PathListBox. layoutPaths> <ec: LayoutPath Distribution = "Even" Orientation = "OrientToPath" SourceElement = "{Binding ElementName = ShortTickPath}"/> </ec: PathListBox. layoutPaths> </ec: PathListBox>

Finally, the scale effect has come out.

4.1.3 text

The scale has been made in the previous section, and a text section is missing. The text part is the same as the scale part, but it is not displayed as a scale. You need to set the style of each Item to TextBlock.

 

<Ed: Arc x: name = "NumberPath" Margin = "20" ArcThickness = "0" ArcThicknessUnit = "Pixel" EndAngle = "120" StartAngle = "-120" Stretch = "None"/> <! -- Number displayed on the scale --> <ec: PathListBox x: Name = "Number" IsHitTestVisible = "False" ItemsSource = "{TemplateBinding NumberListInternal}"> <ec: PathListBox. itemTemplate> <DataTemplate> <TextBlock Text = "{Binding}"/> </DataTemplate> </ec: PathListBox. itemTemplate> <ec: PathListBox. layoutPaths> <ec: LayoutPath Distribution = "Even" Orientation = "OrientToPath" SourceElement = "{Binding ElementName = NumberPath}"/> </ec: PathListBox. layoutPaths> </ec: PathListBox>

 

# Region NumberListInternal number set public IList <object> NumberListInternal {get {return (IList <object>) GetValue (NumberListInternalProperty);} set {SetValue (NumberListInternalProperty, value );}} public static readonly DependencyProperty NumberListInternalProperty = DependencyProperty. register ("NumberListInternal", typeof (IList <object>), typeof (Dashboard); # endregion

The number displayed on the dial is different, so you should set it. Therefore, define a dependency attribute for the maximum and minimum values. The text on the dial should be automatically generated based on these two attributes.

# Region Minimum value // <summary> // Minimum value dependency attribute, used for Binding // </summary> public static readonly DependencyProperty MinimumProperty = DependencyProperty. register ("Minimum", typeof (double), typeof (Dashboard), new PropertyMetadata (0.0); // <summary> // get or set the Minimum value. /// </summary> /// <value> minimum value. </value> public double Minimum {get {return (double) GetValue (MinimumProperty);} set {SetValue (MinimumProperty, value );}} # endregion # region Maximum value // <summary> // Maximum value dependency attribute, used for Binding // </summary> public static readonly DependencyProperty MaximumProperty = DependencyProperty. register ("Maximum", typeof (double), typeof (Dashboard), new PropertyMetadata (100.0); // <summary> // get or set the Maximum value. /// </summary> /// <value> maximum value. </value> public double Maximum {get {return (double) GetValue (MaximumProperty);} set {SetValue (MaximumProperty, value) ;}# endregion

Because the text is only displayed below the Long Scale, the value set in the for loop of Long is

this.NumberListInternal = new List<object>();for (int i = 0; i < this.LongTickCount; i++){this.NumberListInternal.Add(Math.Round(this.Minimum + (this.Maximum - this.Minimum) / (this.LongTickCount - 1) * i));this.LongTicksInternal.Add(i);}

Algorithm Analysis: As mentioned above, we divide the dial scale into eight parts, then (this. maximum-this. minimum)/(this. longTickCount-1) You can get the value represented by each copy. multiplying each copy by I indicates the value of each subsequent copy, but the dial cannot always start from 0, we will set the Minimum value for it, so we have to add Minimum, and the final result may have a decimal point. To save this decimal point, we use Math. the Round () function is used to obtain the integer. Now, the scale and number are complete.

 

4.2 Progress (current value)

This ARC consists of two arcs. Red indicates the current value, and gray is used only for the background color display.

<! -- Complete Arc of the dial --> <ed: Arc x: name = "DoubleCircle" Margin = "50" ArcThickness = "1" ArcThicknessUnit = "Pixel" EndAngle = "120" SnapsToDevicePixels = "True" StartAngle = "-120" Stretch = "None "Stroke =" # 746E7A "StrokeThickness =" 1 "UseLayoutRounding =" True "/> <! -- The Arc corresponding to the current value of the dial --> <ed: Arc x: Name = "PART_IncreaseCircle" Margin = "50" ArcThickness = "1" ArcThicknessUnit = "Pixel" RenderTransformOrigin = "0.5, 0.5 "StartAngle ="-120 "EndAngle =" 10 "Stretch =" None "Stroke =" Yellow "StrokeThickness =" 1 "/>

The effect is as follows:

At this point, the internal structure of the control is basically complete. The last part is to set the angle of the yellow part based on the current value of the dial to achieve the effect of the dashboard. We have defined the maximum Value and the minimum Value, and the difference is the current Value. Therefore, we define the dependency attribute of a Value.
# Current region Value // <summary> // maximum Value dependency attribute, used for Binding // </summary> public static readonly DependencyProperty ValueProperty = DependencyProperty. register ("Value", typeof (double), typeof (Dashboard), new PropertyMetadata (0.0, new PropertyChangedCallback (OnValuePropertyChanged ))); /// <summary> /// get or set the current Value /// </summary> public double Value {get {return (double) GetValue (ValueProperty );} set {SetValue (ValueProperty, value) ;}} private static void OnValuePropertyChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) {// Dashboard dashboard = d as Dashboard; // dashboard. oldAngle = dashboard. angle; // dashboard. setAngle (); // dashboard. transformAngle ();} # endregion

To set the arc Angle, you also need to add an Angle dependency attribute.

#region Anglepublic double Angle{get { return (double)GetValue(AngleProperty); }set { SetValue(AngleProperty, value); }}public static readonly DependencyProperty AngleProperty =DependencyProperty.Register("Angle", typeof(double), typeof(Dashboard), new PropertyMetadata(0d));#endregion

In the code, the Angle is automatically set based on the Value.

private void SetAngle(){var diff = this.Maximum - this.Minimum;var valueDiff = this.Value - this.Minimum;this.Angle = -120 + (120 - (-120)) / diff * valueDiff;}

Algorithm analysis: the end angle-start angle can obtain the angle value of the arc in total, divided by the difference between the maximum value and the minimum value, get the corresponding value of 1 °, multiply the difference between the current value and the minimum value to get the angle sum corresponding to the difference. Because the starting angle is not fixed, the final angle value should be: Starting angle + difference angle and

Finally, we achieved the following results:

One disadvantage is that the starting and ending angles are hard-coded to-120 and 120. for flexibility, you can set them to two dependency attributes, let's do it by yourself. Here we will not post the code.

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.