Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building a custom progress bar for Windows phone 8 App Using XAML and C#

I am trying to make a bar which decreases with time.

The xaml used is

     <Grid x:Name="LayoutRoot" Background="Transparent">
      <TextBlock  Text="Timer :-" FontSize="35" Width="120" Height="70" Margin="138,167,222,531" ></TextBlock>
    <TextBlock x:Name="clock" FontSize="35" Width="100" Height="70" Margin="259,169,121,529" ></TextBlock>

    <Image x:Name="bar" Width="350" Height="20" Source="Assets/progress_bar_bg.png" Margin="65,271,65,477" ></Image>
    <Image x:Name="gbar"  Width="350" Height="20" Source="Assets/green_progress_bar.png" Margin="65,271,65,477" ></Image>

</Grid>

my c# code is

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Navigation;
    using Microsoft.Phone.Controls;
    using System.Windows.Threading;
    using Microsoft.Phone.Shell;

    namespace PhoneApp1
    {
        public partial class ProgressBar : PhoneApplicationPage
        {
    public ProgressBar()
    {
        InitializeComponent();

        newTimer.Interval = TimeSpan.FromSeconds(1);
        newTimer.Tick += OnTimerTick;
        newTimer.Start();
        clock.Text = " 00:60";
    }

    DispatcherTimer newTimer = new DispatcherTimer();
    int counter = 60;

    int width = 350;
    double i=5.8;

    void OnTimerTick(Object sender, EventArgs args)
    {
        counter--;

        if (counter < 0)
        {
            newTimer.Stop();
            counter = 60;
        }
        else
        {


                gbar.Width = width - i;

                clock.Text = " 00:" + counter.ToString();
                i=i+5.8;

            }
        }
    }
}

I have reduced the width such that the size of image green_progress_bar.png should decrease but the problem is that it is reducing from both the ends i want that it sholuld reduced from right to left as the time decrease from 60 sec to 0 sec. And the height is also decreasing with time i want image height to be fixed.

This is the image of what is happening in my case.

enter image description here

enter image description here

enter image description here

I want that the width of the bar should decrease from right to left.But it is decreasing from both the ends. Please help me out.

Thanks in advance.

like image 265
zap92 Avatar asked Apr 16 '14 12:04

zap92


3 Answers

Ok - it's going to be a long post. So first - the result:

ProgressTimer

I've changed a lot in your code (IMO some things might be improved). First of all - the style of the Slider (I've cut out most of the thing that weren't needed for your purpose. The complete template you can find at MSDN or by using Blend):

<phone:PhoneApplicationPage.Resources>
    <Style x:Key="SliderStyle1" TargetType="Slider">
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="BorderBrush" Value="Transparent"/>
        <Setter Property="Maximum" Value="10"/>
        <Setter Property="Minimum" Value="0"/>
        <Setter Property="Value" Value="0"/>
        <Setter Property="Background" Value="{StaticResource PhoneChromeBrush}"/>
        <Setter Property="Foreground" Value="{StaticResource PhoneAccentBrush}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Slider">
                    <Grid Background="Transparent">
                        <Grid x:Name="HorizontalTemplate" Margin="{StaticResource PhoneHorizontalMargin}">
                            <Rectangle x:Name="HorizontalTrack" Fill="{TemplateBinding Background}" Height="12" IsHitTestVisible="False" Margin="0,22,0,50"/>
                            <Rectangle x:Name="HorizontalFill" Fill="GreenYellow" Height="12" IsHitTestVisible="False" Margin="0,22,0,50">
                                <Rectangle.Clip>
                                    <RectangleGeometry Rect="0, 0, 6, 12"/>
                                </Rectangle.Clip>
                            </Rectangle>
                            <Ellipse x:Name="HorizontalCenterElement" HorizontalAlignment="Left" Height="12" Margin="0,16,0,44" Width="12" Fill="GreenYellow">
                                <Ellipse.RenderTransform>
                                    <TranslateTransform/>
                                </Ellipse.RenderTransform>
                            </Ellipse>
                        </Grid>                            
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</phone:PhoneApplicationPage.Resources>

The most important things in the code above are: HorizontalTrack, HorizontalFill and HorizontalCenterElement - you can change the color, shape and so on. I've set centerelement as ellipse so it is a little rounded at the end. The best would be if you just play with it. Using the style goes like this:

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="2*"/>
            <RowDefinition Height="6*"/>
        </Grid.RowDefinitions>
        <Button x:Name="buttonStart" VerticalAlignment="Top" Content="Start" Grid.Row="0"/>
        <TextBlock Name="clock" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding ClockText}" Grid.Row="1"/>
        <Slider Name="gbar" VerticalAlignment="Center" Orientation="Horizontal" HorizontalAlignment="Stretch" 
                Grid.Row="2" Style="{StaticResource SliderStyle1}" IsHitTestVisible="False"
                Value="{Binding ValueLeft, Mode=TwoWay}" Maximum="{Binding MaxValue}"/>

    </Grid>
</Grid>

Few things:

  • I've changed content of UIElements and bound them to properties implementing INotifyPropertyChanged
  • I've disabled Slider for hit testing - so the User won't be able to change it by touching

The rest of the code behind:

public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void RaiseProperty(string property = null)
    { if (this.PropertyChanged != null)                this.PropertyChanged(this, new PropertyChangedEventArgs(property)); }

    private TimeSpan timeLeft = TimeSpan.FromSeconds(60);
    public TimeSpan TimeLeft
    {
        get { return timeLeft; }
        set
        {
            timeLeft = value;
            RaiseProperty("ClockText");
            RaiseProperty("ValueLeft");
        }
    }

    public string ClockText { get { return timeLeft.ToString("m\\:ss"); } }

    public double ValueLeft { get { return TimeLeft.Ticks; } }

    private double maxValue = TimeSpan.FromSeconds(60).Ticks;
    public double MaxValue // number of seconds
    {
        get { return maxValue; }
        set
        {
            TimeLeft = TimeSpan.FromSeconds(value);
            maxValue = TimeLeft.Ticks;
            RaiseProperty("MaxValue");
        }
    }

    System.Threading.Timer newTimer;
    private bool timerStarted = false;

    public MainPage()
    {
        InitializeComponent();
        this.DataContext = this;
        newTimer = new Timer(OnTimerTick);
        buttonStart.Click += buttonStart_Click;
    }

    private void buttonStart_Click(object sender, RoutedEventArgs e)
    {
        if (!timerStarted)
        {
            buttonStart.Content = "STOP";
            MaxValue = 60;
            timerStarted = true;
            newTimer.Change(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1)); // start timer right now and invoke every second
        }
        else
        {
            buttonStart.Content = "Start";
            timerStarted = false;
            newTimer.Change(0, Timeout.Infinite); // stop the timer
        }
    }

    void OnTimerTick(object state)
    { Dispatcher.BeginInvoke(() => TimeLeft -= TimeSpan.FromSeconds(1)); }
}

Few things here:

  • I've organized properties so that now when I change TimeLeft, the TextBlock and Slider are updated - Binding
  • I've changed your DispatcherTimer to System.Threading.Timer - it runs on separate thread, and thus it won't block the UI (when there will be more calculations)
  • because of that separate thread, the changes inside the Callback (on UI) will have to be proceeded via Dispatcher.

This example will surely work with DispatcherTimer fine when your interval is 1 second, but if you plan to perform some more calculations the second Timer may be a better choice. (It is only a different possibility).

like image 165
Romasz Avatar answered Nov 14 '22 07:11

Romasz


Set HorizontalAlignment="Left" for both images. It is center by default.

like image 30
Igor Kulman Avatar answered Nov 14 '22 07:11

Igor Kulman


The problem is in image stretching, it can be disabled by Stretch="None"

<Image x:Name="gbar" Stretch="None" Width="350" Height="20" Source="Assets/green_progress_bar.png" />

Also try not to use Margin for exact positioning, Grid and StackPanel should help.

Instead of making your own progress bar I highly recommend to color/design Microsoft ProgressBar control, as we did it in http://highrobotics.com/we-did-it/wpf-themes.aspx

like image 1
Artur A Avatar answered Nov 14 '22 06:11

Artur A