Skip Navigation LinksHome / Articles / View Article

Creating a simple Voting control in Silverlight 2

+ Add to SilverlightShow Favorites
4 comments   /   posted by Martin Mihaylov on Sep 30, 2008
(96 votes)

Update: The demo above is converted to Silverlight 2 RC0. The only thing you have to change in order to run the code under Silverlight RC0 is to place the generic.xaml in a folder called "Themes" in the project of the voting control. Everything else is compatible with the release candidate.

During the progress of the Silverlight: Write and Win! contest - one has finished and in the end of September another one ended, we thought that it would be nice to create a simple Voting control in Silverlight that will host the poll for the articles in the contest. The custom control we've created is based on the ItemsControl and its items are also custom controls. The biggest advantage of this control is that it's independent on the type of the objects that you pass as ItemsSource. You just set which properties of your business object to be used as Text and as Value for the poll options. That way you don't have to change them according to the control. The control also has a simple progress bar that visualizes the percentage of the votes. 

Source code

Note:

The source code that is available for download is the one used for the article. To create this demo and the voting control for the site I made some modifications to it. The reason I post the basic source code is that you may want to make your own modifications to the control - like allowing multiple votes or only one time voting, modifying the user experience and the styling, adding total votes and poll title, customizing the workflow in the control etc. If you are interested in the things I have modified, I'll gladly share them with you, otherwise you can easily make your own modifications.

Creating the custom control

I suggest you to read the article of Emil Stoychev about Creating a Silverlight Custom Control - The Basics first, if you are not familiar with creating custom controls in Silverlight.

The VotingControl is similar to the ListBox control. There are ListBoxItems in it and in the Voting control we have VotingItemControls. We create a new class file called VotingControl. Our control inherits from ItemsControl and implements INotifyPropertyChanged:

public class VotingControl : ItemsControl, INotifyPropertyChanged
{
    ...
}

Basically the INotifyPropertyChanged interface is used to notify that a property has been changed and thus to force the bound objects to take the new value. And we need that when, for example, the votes count has changed and we have to update the values of the percentage, the progress bar etc. You can see more about this interface here.

Now let's create a default look for the control. First we create the generic.xaml file and add the template there as shown in the code:

Note: If you want to run this on Silverlight Beta 2 place the genric.xaml directly in the project, else if you want to run it on the release candidate, create a folder named "Themes" in your project and place the generic.xaml there.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    xmlns:local="clr-namespace:SilverlightShow.VotingControl;assembly=VotingControl">
 
    <Style TargetType="local:VotingControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:VotingControl">
                    <Grid x:Name="RootElement" Background="White">
                        <ItemsPresenter />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
 
</ResourceDictionary>

The target type of our template is "local:VotingControl", which means that it will be applied to all controls of this type that are present in the project.

We also need a custom control that will present the items in the voting control. It will contain the text of the poll option, the total votes for this option, their percentage and a progress bar. 

[TemplatePart( Name = "rootElement", Type = typeof( FrameworkElement ) )]
[TemplatePart( Name = "itemTextElement", Type = typeof( TextBlock ) )]
[TemplatePart( Name = "valueTextElement", Type = typeof( TextBlock ) )]
[TemplatePart( Name = "percentTextElement", Type = typeof( TextBlock ) )]
public class VotingItemControl : Control, INotifyPropertyChanged
{
    private VotingControl votingControl;
    private FrameworkElement rootElement;
    private TextBlock itemTextElement;
    private TextBlock valueTextElement;
    private TextBlock percentTextElement;
    private ProgressBar progressElement;
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    ...
}

We define in the generic.xaml a template for the controls of type VotingItemControl:

<Style TargetType="local:VotingItemControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:VotingItemControl">
                <Grid x:Name="rootElement" Background="White" Margin="3, 3, 3, 3">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="25"/>
                    </Grid.RowDefinitions>
 
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="70" />
                    </Grid.ColumnDefinitions>
 
                    <TextBlock x:Name="itemTextElement" Grid.Row="0" Grid.Column="0" TextWrapping="Wrap"     
                               VerticalAlignment="Center" HorizontalAlignment="Stretch" FontSize="12" />
                    <TextBlock x:Name="valueTextElement" Grid.Row="0" Grid.Column="1" TextAlignment="Right"
                               VerticalAlignment="Center" HorizontalAlignment="Stretch" FontSize="12" />
                    <TextBlock x:Name="percentTextElement" Grid.Row="1" Grid.Column="1" TextAlignment="Right" 
                               VerticalAlignment="Center" HorizontalAlignment="Stretch" FontSize="12" />
 
                    <local:ProgressBar x:Name="progress" Grid.Row="1" Grid.Column="0"
                                       Minimum="0" Maximum="100"  Background="Azure"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Here are the properties that we need for the both controls:

VotingControl

Properties

  • DisplayTextMember - contains the name of the property that the text is bound to.
  • ValueMember - contains the name of the property that the value is bound to.
  • Total - the total count of votes. The value is calculated on the base of the VotingItemControls in the votingItemControls collection and their values.
  • votingItemControls - a collection of the items that are contained in the VotingControl.

Events

  • PropertyChanged - raised when a property is changed.
  • Voted - raised when one of the VotingItemControls has been clicked.

VotingItemControl

Properties

  • VotingControl - the parent VotingControl that holds this VotingItemControl.
  • TextMember - gets the name of the property that the text is bound to from the parent VotingControl's DisplayTextMemeber property.
  • Text - gets or sets the value of the property of the DataContext object, which name is contained in the TextMember.
  • ValueMember - gets the name of the property that the text is bound to from the parent VotingControl's ValueMemeber property.
  • Value - gets or sets the value of the property of the DataContext object, which name is contained in the ValueMember.
  • Total - gets the value of the Total property of the parent VotingControl
  • Percent - get the value of the current items votes in percents calculated on the base of the total votes for the VotingControl.

Events

  • PropertyChanged - raised when a property is changed.
  • Voted - raises on MouseLeftButton click.

Here are the DisplayTextMember and ValueMember properties of the VotingControl:

public class VotingControl : ItemsControl, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
 
    private string displayTextMember;
    private string valueMember;
 
    public string DisplayTextMember
    {
        get
        {
            return displayTextMember;
        }
        set
        {
            if( displayTextMember == value )
                return;
    
            displayTextMember = value;
            this.OnPropertyChanged( new PropertyChangedEventArgs( "DisplayTextMember" ) );
        }
    }
    
    public string ValueMember
    {
        get
        {
            return this.valueMember;
        }
        set
        {
            if( valueMember == value )
                return;
    
            valueMember = value;
            this.OnPropertyChanged( new PropertyChangedEventArgs( "ValueMember" ) );
        }
    }
}

You can see the implementation of the INotifyPropertyChanged interface. When the property is set and the new value is different than the old one, a method that raises the PropertyChanged event is called. Later we'll see how this event is handled in the VotingItemControl

In the VotingItemControl we create first the VotingControl property:

private VotingControl votingControl;
 
public VotingControl VotingControl
{
    get
    {
        return this.votingControl;
    }
    set
    {
        if ( this.VotingControl == value )
            return;
 
        if ( this.VotingControl != null )
        {
            this.VotingControl.PropertyChanged -= new PropertyChangedEventHandler( VotingControl_PropertyChanged );
            this.VotingControl.Voted -= new EventHandler<VotedEventArgs>( VotingControl_Voted );
        }
 
        this.votingControl = value;
 
        if ( this.VotingControl != null )
        {
            this.VotingControl.PropertyChanged += new PropertyChangedEventHandler( VotingControl_PropertyChanged );
            this.VotingControl.Voted += new EventHandler<VotedEventArgs>( VotingControl_Voted );
        }
 
        this.Refresh();
    }
}

If the new value is the same as the old one, we change nothing. Otherwise we change the value of the property, set the event handlers for the PropertyChanged and Voted events and refresh the information in the UI via the method Refresh().This method will be explained later on in the article.

Now we continue with the TextMember and ValueMemeber properties of the VotingItemControl.

public string TextMember
{
    get
    {
        return this.VotingControl.DisplayTextMember;
    }
}
 
public string ValueMember
{
    get
    {
        return this.VotingControl.ValueMember;
    }
}

We get them via the VotingControl property and that's one of the reasons we need this property - to access the DisplayTextMemeber and ValueMember properties of the parent control. We need their values so we can manage the Text and the Value properties of the VotingItemControl:

public string Text
{
    get
    {
        if ( this.DataContext == null || string.IsNullOrEmpty(this.TextMember) )
            return string.Empty;
 
        PropertyInfo pi = this.DataContext.GetType().GetProperty( this.TextMember );
        if ( pi == null )
            throw new MissingMemberException( this.TextMember );
 
         return pi.GetValue( this.DataContext, null ).ToString();
    }
}
 
public int Value
{
   get
   {
       if ( string.IsNullOrEmpty( this.ValueMember ) == false )
       {
           PropertyInfo pi = this.DataContext.GetType().GetProperty( this.ValueMember );
           if ( pi == null )
               throw new MissingMemberException( this.ValueMember );
 
           return Convert.ToInt32( pi.GetValue( this.DataContext, null ) );
       }
       else
           return 0;
   }
   set
   {
       if ( string.IsNullOrEmpty( this.ValueMember ) == false )
       {
           PropertyInfo pi = this.DataContext.GetType().GetProperty( this.ValueMember );
           if ( pi == null )
               throw new MissingMemberException( this.ValueMember );
 
           int votes = Convert.ToInt32( pi.GetValue( this.DataContext, null ) );
 
           pi.SetValue( this.DataContext, votes + 1, null );
       }
 
       this.OnPropertyChanged( new PropertyChangedEventArgs( "Value" ) );
       this.OnPropertyChanged( new PropertyChangedEventArgs( "Total" ) );
       this.OnPropertyChanged( new PropertyChangedEventArgs( "Percent" ) );
   }
}

We use the PropertyInfo class, the DataContext and reflection to set or get these properties. The PropertyChanged event is raised three times when we set the Value property. That's because these three properties are dependent on the change. Here are the Percent and the Total properties:

public int Total
{
    get
    {
        return this.VotingControl.Total;
    }
}
 
public decimal Percent
{
    get
    {
        return this.Total == 0 ? 0 : ( ( decimal )this.Value / ( decimal )this.Total ) * 100;
    }
}

The Total property of the VotingItemControl gets the Total property of its parent VotingControl:

public int Total
{
    get
    {
        int total = 0;
        foreach( VotingItemControl vi in this.votingItemControls )
            total += vi.Value;
 
        return total;
    }
}

Events and methods

Now let's discuss the events in our control. There are two events that are raised from both the VotingControl and the VotingItemControl - PropertyChanged and Voted. We handle the PropertyChanged event for the VotingControl in the VotingItemControl and raise it again, this time in the VotingItemControl:

void VotingControl_PropertyChanged( object sender, PropertyChangedEventArgs e )
{
    if ( e.PropertyName == "DisplayTextMember" )
    {
        this.OnPropertyChanged( new PropertyChangedEventArgs( "Text" ) );
    }
 
    if ( e.PropertyName == "ValueMember" )
    {
        this.OnPropertyChanged( new PropertyChangedEventArgs( "Value" ) );
        this.OnPropertyChanged( new PropertyChangedEventArgs( "Total" ) );
        this.OnPropertyChanged( new PropertyChangedEventArgs( "Percent" ) );
    }
 
    this.Refresh();
}

Looking back you can see that the ValueMember and DiplayTextMember properties of the VotingControl raise this event when they are being set. Depending on the argument, events for each property that is affected by this change are fired in the VotingItemControl. After that we refresh the UI:

private void Refresh()
{
    if ( this.progressElement != null )
        this.progressElement.Value = Convert.ToInt32( this.Percent );
 
    if ( this.itemTextElement != null )
        this.itemTextElement.Text = this.Text;
 
    if ( this.valueTextElement != null )
        this.valueTextElement.Text = this.Value.ToString();
 
    if ( this.percentTextElement != null )
        this.percentTextElement.Text = this.Percent.ToString("0.00") + " %";
}

The PropertyChanged event is raised also when we set a property in the VotingItemControl, Value for example. The Percent and Total properties are changed because they depend on the Value property. Note that because the event raised in the VotingControl is handled in all of its child VotingItemControls.

The Voted event is also raised in both controls. First it fires in the VotingItemControl that is clicked for example:

private void rootElement_MouseLeftButtonUp( object sender, MouseButtonEventArgs e )
        {
            this.Value += 1;
            this.OnVoted();
        }

Then it's handled in the VotingControl:

void VotingItem_Voted( object sender, EventArgs e )
{
    this.OnVoted( new VotedEventArgs( ( VotingItemControl )sender ) );
    this.OnPropertyChanged( new PropertyChangedEventArgs( "Total" ) );
}

You can see that the VotingControl raises its Voted event and PropertyChanged event for the Total property. The idea here is that when the user gives his/her vote this affects all VotingItemControls in our VotingControl - they have to update their percentage. Via the VotingControl property we can handle the Voted event in all of them and force them to update their Total and Percent properties.

void VotingControl_Voted( object sender, VotedEventArgs e )
{
    this.OnPropertyChanged( new PropertyChangedEventArgs( "Total" ) );
    this.OnPropertyChanged( new PropertyChangedEventArgs( "Percent" ) );
 
    this.Refresh();
}

Configuring the VotingControl to contain VotingItemControls

We want the items in our VotingControl to be of type VotingItemControl. For that purpose we have to override a few methods. These methods are protected and are used by the ItemsControl when setting the ItemsSource, so we adjust them to be compatible with the VotingItemControl:

protected override void PrepareContainerForItemOverride( DependencyObject element, object item )
{
    base.PrepareContainerForItemOverride( element, item );
    if( element is VotingItemControl )
    {
        VotingItemControl item2 = ( ( VotingItemControl )element );
        item2.VotingControl = this;
        item2.Voted += new EventHandler( VotingItem_Voted );
        item2.DataContext = item;
        this.votingItemControls.Add( item2 );
    }
}
 
protected override void ClearContainerForItemOverride( DependencyObject element, object item )
{
    base.ClearContainerForItemOverride( element, item );
    if( element is VotingItemControl && this.votingItemControls.Contains( ( VotingItemControl )element ) )
        this.votingItemControls.Remove( ( VotingItemControl )element );
}
 
protected override DependencyObject GetContainerForItemOverride()
{
    VotingItemControl item = new VotingItemControl();
    return item;
}

The GetContainerFroItem returns an object of type VotingItemControl, this means that all of the items in our VotingControl will be of that type. ClearContainerForItem clears the items and the PrepareContainerForItem configures the VotingItemControls. You can see that the objects from the ItemsSource collection are set as DataContexts to the VotingItemControls, an event handler for the Voted event is added and each element is added to the votingItemControls collection.

That's all. In the demo you'll see a progress bar for each voting option. You can find it in the source code. In it you'll find the control and take a look at the code for better understanding and for additional information about the control.

 

Summary

These are the most important things about the VotingControl we've created. You can find the full source code here and don't miss the demo. If you have any questions, don't hesitate to ask and I will be glad to answer you. This control is now available for real-time voting for the Silverlight: Write and Win! contest on SilverlightShow.net.

 

Share


Comments

Comments RSS RSS
  • RE: Creating a simple Voting control in Silverlight 2  

    posted by dd on Nov 18, 2008 10:14
  • RE: Creating a simple Voting control in Silverlight 2  

    posted by J on Jul 01, 2009 22:11
    I'm pretty new at this, how did you change the base code to make it so that people can only vote once? Please and Thank you.
  • RE: Creating a simple Voting control in Silverlight 2  

    posted by David on Sep 08, 2009 17:30
    I'm interesting on the same matter... how did you change the code to allow just one vote?, When can I vote again?
  • how to use in game vote  

    posted by hen on May 25, 2010 18:11

    i am asking how to use to vote in www.xilero.net

    thanks i need tutorial to use this program thanks

Add Comment

 
 

   
  
  
   
Please add 5 and 3 and type the answer here:

Join the free SilverlightShow webcast 'Running Silverlight Outside the Browser and with Elevated Trust'. Sept 7th, 8 am - 9 am PDT.
In this live session Chris Anderson will cover configuring and debugging OOB mode, toast notifications, elevated trust, direct file access and much more.
Learn more | Register | See more webinars (hide this)