WPF甜甜圈进度 [英] WPF Doughnut ProgressBar

查看:135
本文介绍了WPF甜甜圈进度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图适应在书中WPF 4偷跑看起来像甜甜圈发现馅饼进度。我觉得我中途有,但我不知道如何解决最后一个问题。



下面是图片说明我想要什么,我已成功地实现






  1. 这是我怎么想它关注一下。

  2. 这是它看起来像使用下面的代码。

  3. 我发现了一个建议,在这里计算器一个不同的问题这是使用裁剪的道路上,并增加一倍的笔触粗细。正如你所看到的路径是否正确定位,但现在50%以下任何进展不正确绘制,你可以看到。



所以我的?问题是,如何解决它看起来像我想要



下面是相关的XAML我使用:

 <控件模板X:键=DonutProgressBar的TargetType ={X:类型进度}> 
< ControlTemplate.Resources>
< CONV:ValueMinMaxToIsLargeArcConverter X:键=ValueMinMaxToIsLargeArcConverter/>
< CONV:ValueMinMaxToPointConverter X:键=ValueMinMaxToPointConverter/>
< /ControlTemplate.Resources>
<网格和GT;
<&Viewbox控件GT;
<电网WIDTH =20HEIGHT =20>
<椭圆X:NAME =背景
行程={TemplateBinding BorderBrush}
StrokeThickness ={绑定的RelativeSource = {的RelativeSource TemplatedParent},路径= BorderThickness.Top}
WIDTH =20
高度=20
填充={TemplateBinding背景}/>
<路径X:NAME =甜甜圈
行程={TemplateBinding前景}
StrokeThickness ={绑定的RelativeSource = {的RelativeSource TemplatedParent},路径= BorderThickness.Top}> ;
< Path.Data>
<&的PathGeometry GT;
< PathGeometry.Figures>
<的PathFigure StartPoint可以=10,0>
< ArcSegment大小=10,10SweepDirection =顺时针>
< ArcSegment.Point>
< MultiBinding转换器={StaticResource的ValueMinMaxToPointConverter}>
<绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=值/>
<绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=最小/>
<绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=最大/>
< / MultiBinding>
< /ArcSegment.Point>
< ArcSegment.IsLargeArc>
< MultiBinding转换器={StaticResource的ValueMinMaxToIsLargeArcConverter}>
<绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=值/>
<绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=最小/>
<绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=最大/>
< / MultiBinding>
< /ArcSegment.IsLargeArc>
< / ArcSegment>
< /&的PathFigure GT;
< /PathGeometry.Figures>
< /&的PathGeometry GT;
< /Path.Data>
< /路径和GT;
< /网格和GT;
< / Viewbox控件>
< /网格和GT;
< /控件模板>


<进度WIDTH =70HEIGHT =70VALUE =40模板={StaticResource的DonutProgressBar}后台={X:空} BorderBrush =#1F000000了borderThickness =6,6,1,1/>



...和转换器:

 公共类ValueMinMaxToPointConverter:IMultiValueConverter 
{
公共对象转换(对象[]值类型TARGETTYPE,对象参数,CultureInfo的文化)
{
双击值=(双)值[0];
双最小=(双)值[1];
双最大=(双)值[2];

双电流=(值/(最大值 - 最小值))* 360;

如果(当前== 360)
电流= 359.999;

电流=电流 - 90;

电流=电流*(Math.PI / 180.0);

双X = 10 + 10 * Math.Cos(电流);
双Y = 10 + 10 * Math.Sin(电流);

返回新的点(X,Y);
}

公共对象[] ConvertBack(对象的值,类型[] TARGETTYPE,对象参数,CultureInfo的文化)
{
抛出新NotSupportedException异常();
}
}

公共类ValueMinMaxToIsLargeArcConverter:IMultiValueConverter
{
公共对象转换(对象[]值类型TARGETTYPE,对象参数,CultureInfo的文化)
{
双重价值=(双)值[0];
双最小=(双)值[1];
双最大=(双)值[2];

回报率((值* 2)> =(最高 - 最低));
}

公共对象[] ConvertBack(对象的值,类型[] TARGETTYPE,对象参数,CultureInfo的文化)
{
抛出新NotSupportedException异常();
}
}


解决方案

您代码是非常接近的。问题不削波中的一个。你还根本没有考虑到,当路径抚摸,行程绘制中心的路径上。这意味着,几何,行程本身必须在的,你想让它绘制的。



在您的特定实现,这意味着你需要在三个不同的地方来解释行程厚度



<醇>
  • 弧的起点。起始点需要被垂直偏移以考虑笔划厚度

  • 弧的尺寸。需要降低电弧的大小,以便路径保持在较大的圆的冲程中心

  • 圆弧的终点。与开始点,这需要进行调整,但在这种情况下,它是在计算电弧,需要调整的半径。



  • 例如,您可以添加一对夫妇的转换器:

     类ThicknessToStartPointConverter:的IValueConverter 
    {
    酒店的公共对象转换(对象的值,类型TARGETTYPE,对象参数,System.Globalization.CultureInfo文化)
    {
    如果(!(值加倍))
    {
    返回绑定。没做什么;
    }

    //需要启动弧预期中风
    中旬返回新的点(10,((双)值)/ 2);
    }

    公共对象ConvertBack(对象的值,类型TARGETTYPE,对象参数,System.Globalization.CultureInfo文化)
    {
    抛出新NotImplementedException();
    }
    }

    类ThicknessToSizeConverter:的IValueConverter
    {
    公共对象转换(对象的值,类型TARGETTYPE,对象参数,System.Globalization.CultureInfo文化)
    {
    如果((值是双倍!))
    {
    返回Binding.DoNothing;
    }

    双widthHeight = 10 - ((双)值)/ 2;

    返回新的大小(widthHeight,widthHeight);
    }

    公共对象ConvertBack(对象的值,类型TARGETTYPE,对象参数,System.Globalization.CultureInfo文化)
    {
    抛出新NotImplementedException();
    }
    }



    ,然后更新您的XAML看起来像这样:

     <的PathFigure StartPoint可以={结合StrokeThickness,的ElementName =甜甜圈,转换器= {StaticResource的thicknessToStartPointConverter}}> 
    < ArcSegment大小={结合StrokeThickness,的ElementName =甜甜圈,转换器= {StaticResource的thicknessToSizeConverter}}SweepDirection =顺时针>
    < ArcSegment.Point>
    < MultiBinding转换器={StaticResource的ValueMinMaxToPointConverter}>
    <绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=值/>
    <绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=最小/>
    <绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=最大/>
    <绑定路径=StrokeThickness的ElementName =甜甜圈/>
    < / MultiBinding>
    < /ArcSegment.Point>
    < ArcSegment.IsLargeArc>
    < MultiBinding转换器={StaticResource的ValueMinMaxToIsLargeArcConverter}>
    <绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=值/>
    <绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=最小/>
    <绑定的RelativeSource ={的RelativeSource TemplatedParent}路径=最大/>
    < / MultiBinding>
    < /ArcSegment.IsLargeArc>
    < / ArcSegment>
    < /&的PathFigure GT;

    使用,当然,对于转换器所需的资源:



     < L:ThicknessToStartPointConverter X:键=thicknessToStartPointConverter/> 
    < L:ThicknessToSizeConverter X:键=thicknessToSizeConverter/>



    然后你会得到你想要的东西。



    有可能是撰写背景的方式椭圆元素和路径元素在一起,使得路径绘制无上述情况,即用10的硬编码的大小,然后让电网同样调整子元素,使它们正确对齐。但我没有看到沿着这些线路的任何明显的解决方案,并没有觉得像花弄明白的时间。以上应为您的目的很好地工作。 :)


    I am trying to adapt the pie ProgressBar found in the book WPF 4 Unleashed to look like a doughnut. I feel I am half way there but I don't know how to solve the last problem.

    Here is a picture illustrating what I want and what I have managed to achieve:

    1. This is how I want it to look.
    2. This is what it looks like using the code below.
    3. I found a suggestion in a different question here at stackoverflow which was to use clipping on the path and double the stroke thickness. As you can see the path is positioned correctly now but any progress below 50% is not drawn correctly as you can see.

    So my question is, how can I fix it to look like I want?

    Below is the relevant xaml I am using:

    <ControlTemplate x:Key="DonutProgressBar" TargetType="{x:Type ProgressBar}">
        <ControlTemplate.Resources>
            <conv:ValueMinMaxToIsLargeArcConverter x:Key="ValueMinMaxToIsLargeArcConverter" />
            <conv:ValueMinMaxToPointConverter x:Key="ValueMinMaxToPointConverter" />
        </ControlTemplate.Resources>
        <Grid>
            <Viewbox>
                <Grid Width="20" Height="20">
                    <Ellipse x:Name="Background"
                             Stroke="{TemplateBinding BorderBrush}"
                             StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness.Top}"
                             Width="20"
                             Height="20"
                             Fill="{TemplateBinding Background}" />
                    <Path x:Name="Donut" 
                          Stroke="{TemplateBinding Foreground}"
                          StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness.Top}">
                        <Path.Data>
                            <PathGeometry>
                                <PathGeometry.Figures>
                                    <PathFigure StartPoint="10,0">
                                        <ArcSegment Size="10,10" SweepDirection="Clockwise">
                                            <ArcSegment.Point>
                                                <MultiBinding Converter="{StaticResource ValueMinMaxToPointConverter}">
                                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
                                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
                                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
                                                </MultiBinding>
                                            </ArcSegment.Point>
                                            <ArcSegment.IsLargeArc>
                                                <MultiBinding Converter="{StaticResource ValueMinMaxToIsLargeArcConverter}">
                                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
                                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
                                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
                                                </MultiBinding>
                                            </ArcSegment.IsLargeArc>
                                        </ArcSegment>
                                    </PathFigure>
                                </PathGeometry.Figures>
                            </PathGeometry>
                        </Path.Data>
                    </Path>
                </Grid>
            </Viewbox>
        </Grid>
    </ControlTemplate>
    
    ...
    <ProgressBar Width="70" Height="70" Value="40" Template="{StaticResource DonutProgressBar}" Background="{x:Null}" BorderBrush="#1F000000"  BorderThickness="6,6,1,1" />
    

    ...and the converters:

    public class ValueMinMaxToPointConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double value = (double)values[0];
            double minimum = (double)values[1];
            double maximum = (double)values[2];
    
            double current = (value / (maximum - minimum)) * 360;
    
            if (current == 360)
                current = 359.999;
    
            current = current - 90;
    
            current = current * (Math.PI / 180.0);
    
            double x = 10 + 10 * Math.Cos(current);
            double y = 10 + 10 * Math.Sin(current);
    
            return new Point(x, y);
        }
    
        public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    
    public class ValueMinMaxToIsLargeArcConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double value = (double)values[0];
            double minimum = (double)values[1];
            double maximum = (double)values[2];
    
            return ((value * 2) >= (maximum - minimum));
        }
    
        public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    

    解决方案

    Your code is very close. The problem is not one of clipping. You have simply failed to take into account that when paths are stroked, the stroke is drawn centered on the path. This means that geometrically, the stroke itself must be in the middle of where you want it drawn.

    In your particular implementation, this means you need to account for the stroke thickness in three different places:

    1. The start point of the arc. The start point needs to be offset vertically to account for the stroke thickness.
    2. The size of the arc. The size of the arc needs to be reduced, so that the path remains centered in the stroke of the larger circle.
    3. The end point of the arc. As with the start point, this needs to be adjusted, but in this case it is the radius of the arc in your calculation that needs adjusting.

    For example, you can add a couple of converters:

    class ThicknessToStartPointConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (!(value is double))
            {
                return Binding.DoNothing;
            }
    
            // Need to start the arc in the middle of the intended stroke
            return new Point(10, ((double)value) / 2);
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    class ThicknessToSizeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (!(value is double))
            {
                return Binding.DoNothing;
            }
    
            double widthHeight = 10 - ((double)value) / 2;
    
            return new Size(widthHeight, widthHeight);
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    And then update your XAML to look like this:

    <PathFigure StartPoint="{Binding StrokeThickness, ElementName=Donut, Converter={StaticResource thicknessToStartPointConverter}}">
      <ArcSegment Size="{Binding StrokeThickness, ElementName=Donut, Converter={StaticResource thicknessToSizeConverter}}" SweepDirection="Clockwise">
        <ArcSegment.Point>
          <MultiBinding Converter="{StaticResource ValueMinMaxToPointConverter}">
            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
            <Binding Path="StrokeThickness" ElementName="Donut"/>
          </MultiBinding>
        </ArcSegment.Point>
        <ArcSegment.IsLargeArc>
          <MultiBinding Converter="{StaticResource ValueMinMaxToIsLargeArcConverter}">
            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
          </MultiBinding>
        </ArcSegment.IsLargeArc>
      </ArcSegment>
    </PathFigure>
    

    With, of course, the necessary resources for the converters:

    <l:ThicknessToStartPointConverter x:Key="thicknessToStartPointConverter"/>
    <l:ThicknessToSizeConverter x:Key="thicknessToSizeConverter"/>
    

    And then you will get what you want.

    There's probably a way to compose the background Ellipse element and Path element together, such that the Path is drawn without the above, i.e. with the hard-coded sizes of 10, and then have the Grid resize both child elements equally, causing them to line up correctly. But I didn't see any obvious solutions along those lines and didn't feel like spending the time to figure it out. The above should work fine for your purposes. :)

    这篇关于WPF甜甜圈进度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆