This blog post describes how to re-template the Silverlight ProgressBar control to render a circular progress indicator. This approach uses an attached view model to circumnavigate some of the limitations of the ProgressBar design.

This blog post describes the creation of the following funky styles for the ProgressBar (and a bit of a rant about just what is wrong with the way that 'lookless' controls work!)

Get Microsoft Silverlight

If you get sick of the spinning, click the pause button!

INTRODUCTION

A few days ago I answered a question on stack overflow which asked How create a Circular Style progressbar? (sic)

The answer which I gave, and most people seemed to agree with, was that to achieve this you would have to create your own control from 'scratch'. I was happy that my answer was accepted, but at the same time a little unhappy that this should be the right answer. After all, Silverlight / WPF give you the power to create 'lookless' controls, and what is a circular progress bar if it isn't just another skin or 'look' for the regular progress bar?

WHAT IS WRONG WITH THE PROGRESSBAR?

If you look at the documentation for styling / templating the ProgressBar you will find that this control expects the template to contain two elements, ProgressBarTrack and ProgressBarIndicator:

What the ProgressBar does is, when the template is applied, in OnApplyTemplate, it locates the elements with the given names in order to update the visual state of the UI. You can use Reflector (quick, while it is still free!) to see how the state of these elements is updated in the ProgressBar.SetProgressBarIndicatorLength method:

private void SetProgressBarIndicatorLength()

{

double minimum = base.Minimum;

double maximum = base.Maximum;

double num3 = base.Value;

if ((this.ElementTrack != null) && (this.ElementIndicator != null))

{

FrameworkElement parent = VisualTreeHelper.GetParent(this.ElementIndicator) as FrameworkElement;

if (parent != null)

{

double num4 = this.ElementIndicator.Margin.Left + this.ElementIndicator.Margin.Right;

Border border = parent as Border;

if (border != null)

{

num4 += border.Padding.Left + border.Padding.Right;

}

else

{

Control control = parent as Control;

if (control != null)

{

num4 += control.Padding.Left + control.Padding.Right;

}

}

double num5 = (this.IsIndeterminate || (maximum == minimum)) ? 1.0 : ((num3 - minimum) / (maximum - minimum));

double num6 = Math.Max((double) 0.0, (double) (parent.ActualWidth - num4));

this.ElementIndicator.Width = num5 * num6;

}

}

}

You can see in the above code that the various properties of the ElementTrack and ElementIndicator elements (the two named elements in the template) are being updated programmatically. This basically restricts the re-templating capabilities of the ProgressBar to ones where the 'indicator' element has a width which is some proportion of its parent element. That is not very lookless!

So what is so bad about creating your own circular progress indicator from scratch? Firstly, there is the issue of object-oriented design principles and re-use. Secondly, and in my opinion much more importantly, is how this affects skinning. Templating allows you to radically change your UI simply by applying a new set of styles, see for example the Silverlight Toolkit Themes. Styles can change the value of any property of an element (including its template) but they cannot change the class itself! So, if you create a circular progress bar as a new control, you cannot interchange it with the standard ProgressBar simply by applying a theme.

AN ATTACHED VIEW MODEL

OK, rant over. Time to fix the problem!

A few months ago I blogged about how to create completely lookless controls using an attached view model. The basic concept behind this approach is that the control itself should not include any logic which is tightly-coupled to a particular template, or 'look'. This logic is still required, but is instead introduced into the template by means of an attached view model.

Typically the elements within a control's template inherit the same DataContext as the control itself, i.e. whatever business object or view model you have bound to your UI. With the attached view model approach, a view model is attached to the root element in the template. On attachment, this view model acquires a reference to the ProgressBar, in order to adapt its properties, making it easier to render a circular indicator, and sets itself as the DataContext of the child elements:

The view model is attached in XAMl as follows, as a result the DataContext of any element within the template is now the view model:

<!-- the rest of the template now has CircularProgressBarViewModel as the DataContext -->

BECOMING ATTACHED
The changed handler for the Attach property is given below. In summary, on attachment, the view model sets itself as the DataContext for the element it has been attached to. It then handlers the Loaded event which fires when the UI is fully constructed in order to locate the ProgressBar using Linq to VisualTree:

///



/// Change handler for the Attach property

///

private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

// set the view model as the DataContext for the rest of the template

FrameworkElement targetElement = d as FrameworkElement;

CircularProgressBarViewModel viewModel = e.NewValue as CircularProgressBarViewModel;

targetElement.DataContext = viewModel;

// handle the loaded event

targetElement.Loaded += new RoutedEventHandler(Element_Loaded);

}

///



/// Handle the Loaded event of the element to which this view model is attached

/// in order to enable the attached

/// view model to bind to properties of the parent element

///

static void Element_Loaded(object sender, RoutedEventArgs e)

{

FrameworkElement targetElement = sender as FrameworkElement;

CircularProgressBarViewModel attachedModel = GetAttach(targetElement);

// find the ProgressBar and associated it with the view model

var progressBar = targetElement.Ancestors().Single() as ProgressBar;

attachedModel.SetProgressBar(progressBar);

}

Once the view model is associated with the progress bar, it is able to compute properties which assist in the creation of a circular template, e.g. the angle used to represent a particular progress value.

///



/// Add handlers for the updates on various properties of the ProgressBar

///

private void SetProgressBar(ProgressBar progressBar)

{

_progressBar = progressBar;

_progressBar.SizeChanged += (s, e) => ComputeViewModelProperties();

RegisterForNotification("Value", progressBar, (d,e) => ComputeViewModelProperties());

RegisterForNotification("Maximum", progressBar, (d, e) => ComputeViewModelProperties());

RegisterForNotification("Minimum", progressBar, (d, e) => ComputeViewModelProperties());

ComputeViewModelProperties();

}

/// Add a handler for a DP change

/// see: http://amazedsaint.blogspot.com/2009/12/silverlight-listening-to-dependency.html

private void RegisterForNotification(string propertyName, FrameworkElement element, PropertyChangedCallback callback)

{

//Bind to a dependency property

Binding b = new Binding(propertyName) { Source = element };

var prop = System.Windows.DependencyProperty.RegisterAttached(

"ListenAttached" + propertyName,

typeof(object),

typeof(UserControl),

new PropertyMetadata(callback));

element.SetBinding(prop, b);

}

Thanks to Anoop for publishing a nice and simple method for registering for change notification of dependency properties (what a pain that DPs do not also implement the INotifyPropertyChanged pattern!).

Each time one of the properties on the progress bar changes, the following method updates a few of the CLR properties exposed by the attached view model:

///



/// Re-computes the various properties that the elements in the template bind to.

///

protected virtual void ComputeViewModelProperties()

{

if (_progressBar == null)

return;

Angle = (_progressBar.Value - _progressBar.Minimum) * 360 / (_progressBar.Maximum - _progressBar.Minimum);

CentreX = _progressBar.ActualWidth / 2;

CentreY = _progressBar.ActualHeight / 2;

Radius = Math.Min(CentreX, CentreY);

Diameter = Radius * 2;

InnerRadius = Radius * HoleSizeFactor;

Percent = Angle / 360;

}

The complete XAML for one of the styled progress bars seen at the top of this blog post is given below. Here you can see how the various UI elements within the template are bound to the attached view model:

(The template uses a PiePiece is a control I borrowed from a PieChart control I created a few years back, and the simplified Grid syntax)

We now have a circular ProgressBar! ...

SEGMENTED PROGRESS BAR

For a bit of fun I extended the attached view model to allow for the easy construction of circular progress bar sthat are rendered as discrete segments. The SegmentedProgressBarViewModel, which is attached to the template exposes a collection of objects which allow the creation of a segmented indicator via an ItemsControl. For full details,download the blog sourcecode.

The above markup results in the following style:

The sourcecode for this blog includes a few other styles, including a 'glass' effect which was borrowed from Pete Brown's blog post on Pie chart styling.

SOURCECODE

You can download the full sourcecode for this blog post: CircularProgressBar.zip

A CIRCULAR PROGRESSBAR STYLE USING AN ATTACHED VIEWMODEL的更多相关文章

  1. Some beautiful Progress Bars in WPF

    1.Better WPF Circular Progress Bar 2.Bending the WPF ProgressBar 3.A CIRCULAR PROGRESSBAR STYLE USIN ...

  2. [Android UI] ProgressBar自定义

    转载自:http://gundumw100.iteye.com/blog/1289348 1: 在JAVA代码中 在java代码中 ProgressBar      继承自View, 在android ...

  3. 自定义ProgressBar的加载效果

    三种方式实现自定义圆形页面加载中效果的进度条 To get a ProgressBar in the default theme that is to be used on white/light b ...

  4. Android ProgressBar具体解释以及自己定义

       版本号:1.0 日期:2014.5.16 版权:© 2014 kince 转载注明出处   这一次主要说一下Android下的进度条.为什么是它呢,由于最近被其各种美轮美奂的设计所倾倒,计划逐渐 ...

  5. The Amazing ProgressBar Control(转)

    好久没写博客了,今天就先转一篇,随后可以再写些~~~ 直接把原文粘过来,就不再进行翻译和个人说明了,因为效果很COOL~ The Amazing ProgressBar Control A progr ...

  6. 使用android ProgressBar和Toast生成一个界面

    首先我需要这样一个界面 这个界面是在使用AudioManager.adjustStreamVolume(int streamType, int direction, int flags)显示出来的,记 ...

  7. android:style.xml

    <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2006 The Andr ...

  8. ProgressBar的Indeterminate属性

    Indeterminate ProgressBar默认是白色的,如果容器的背景也是白色的,这样就根本看不到Progressbar. 简单解决方案: 用style属性设定反转的颜色. <Progr ...

  9. Android(java)学习笔记130:ProgressBar使用的

    首先我们看例程如下: 1.main.xml文件如下: <?xml version="1.0" encoding="utf-8"?> <Line ...

随机推荐

  1. .android:allowTaskReparenting 等Activity 的task属性

    转自http://blog.csdn.net/javayinjaibo/article/details/8855678 1.android:allowTaskReparenting 这个属性用来标记一 ...

  2. 2015年8月17日,杨学明老师《产业互联网化下的研发模式转型》在中国科学院下属机构CNNIC成功举办!

    2015年8月17日,杨学明老师为中国网络新闻办公室直属央企中国互联网络中心(CNNIC)提供了一天的<产业互联网化下的研发模式转型>内训课程.杨学明老师分别从产业互联网化的问题与挑战.传 ...

  3. springmvc之interceptor(拦截器)

    1.自定义MyInterceptor impletments HandlerInterceptor public class MyInterceptor implements HandlerInter ...

  4. linux-5重要进程守护

    当给一台主机安装上linux系统后可以工作了-包括接受用户的输入/计算/存储/再将结果输出等等,这都是系统服务帮助我们完成的.而有一些系统服务时刻等待用户的输入(r如键盘进程)或随时响应用户的请求(如 ...

  5. Jeasyframe 开源框架 稳定版 V1.5 发布

    这是Jeasyframe开源框架的第一个稳定版本,感谢一起帮忙测试并给予反馈的网友们. 框架官网:http://www.jeasyframe.org/ 产品介绍: Jeasyframe开源框架是基于S ...

  6. 一步一步搭建客服系统 (4) 客户列表 - JS($.ajax)调用WCF 遇到的各种坑

    本文以一个生成.获取“客户列表”的demo来介绍如何用js调用wcf,以及遇到的各种问题. 1 创建WCF服务 1.1 定义接口 创建一个接口,指定用json的格式:   [ServiceContra ...

  7. PHP 开发社区微信服务号实战图解

    本博文就月初刚上线的微信服务号,图文进行总结分享给大家. 去年年底,我所在的团队讨论要开发微信号,话题由此拉开: 原来有一个3年前注册的微信号,但是后台操作无法从“订阅号”变更为“服务号”,随即找腾讯 ...

  8. [Android] Android Sutdio on Surface Pro 3

    Install Android Studio http://www.android-studio.org/index.php/download/androidstudio-download-baidu ...

  9. latex数字加粗后变宽

    latex的数字默认用的是Times New Roman字体,这个字体有个不优美之处就是加粗后会变宽,如下图所示: 平常倒是也无所谓.昨天在把实验数据整理进表格时,为了凸显每个数据集上各个实验方法的优 ...

  10. iOS开发-友盟分享(3)

    iOS 友盟分享 这个主要是提到如何通过友盟去自定义分享的步骤: 一.肯定要去友盟官网下载最新的SDK包,然后将SDK导入到你的工程文件夹里面去: 二.注册友盟账号,将你的APP添加到你的账号里面然后 ...