views:

444

answers:

1

Hope someone can help. I have a simple scenario where clicking checkboxes is driving a progress bar in WPF. The checkboxes are contained in a UserControl and the Progress bar is in a simple WPF client window. On the user control I am using two dependency properties: 1) the existing Tag property has the value I wish to bind to the progress bar value and 2) a DP called CbCount which represents the total number of checkboxes.

The problem: When the application runs the progress bar's progress shows as being 100% complete even though via Snoop I can see the value is in fact 0. Clicking on the checkboxes everything works fine as expected.

Code: UserControl - within namespace ProgBarChkBxs:

public partial class ucChkBoxes : UserControl
{
    #region CbCount

    public static readonly DependencyProperty CbCountProperty =
        DependencyProperty.Register("CbCount", typeof(double), typeof(ucChkBoxes),
            new FrameworkPropertyMetadata((double)0));

    /// <summary>
    /// Gets or sets the CbCount property.  This dependency property
    /// indicates the number of checkBoxes
    /// </summary>
    public double CbCount
    {
        get { return (double)GetValue(CbCountProperty); }
        private set { SetValue(CbCountProperty, value); }
    }

    #endregion

    double _totalCount = 0;
    double _numberChecked = 0;
    double DEFAULT = 0;

    public ucChkBoxes()
    {
        InitializeComponent();
        this.Tag = DEFAULT;
        this.Loaded += new RoutedEventHandler(ucChkBoxes_Loaded);
    }

    void ucChkBoxes_Loaded(object sender, RoutedEventArgs e)
    {
        if (this.ourContainer.Children.Count != 0)
        {
            _totalCount = this.ourContainer.Children.Count;
        }
        this.CbCount = (double)_totalCount;
    }

    private void CheckBox_Checked(object sender, RoutedEventArgs e)
    {
        if (e.OriginalSource.GetType() == typeof(CheckBox))
        {
            CheckBox cb = (CheckBox)e.OriginalSource;
            if (cb.IsChecked == true) { _numberChecked++; }
            if (cb.IsChecked != true) { _numberChecked--; }

            //simple POC progress metric
            this.Tag = (double)(_numberChecked / _totalCount * _totalCount);
        }
    }
}

XAML:

<UserControl x:Class="ProgBarChkBxs.ucChkBoxes"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="Auto" Width="Auto">
    <StackPanel>
        <TextBlock  Text="Please select options" ></TextBlock>
        <StackPanel Name="ourContainer"
                    CheckBox.Checked="CheckBox_Checked"
                    CheckBox.Unchecked="CheckBox_Checked">
            <CheckBox>Fruit Juice</CheckBox>
            <CheckBox>Coffee</CheckBox>
            <CheckBox>Toast</CheckBox>
            <CheckBox>Cereal</CheckBox>
            <CheckBox>Grapefruit</CheckBox>
        </StackPanel>
    </StackPanel>
</UserControl>

The Client which just has the databindings is a simple window - the local namespace below refers to the project namespace xmlns:local="clr-namespace:ProgBarChkBxs", the meat of the code is:

<StackPanel>
    <local:ucChkBoxes  x:Name="chkBoxes"/>
    <ProgressBar Name="pb" Background="Azure" Minimum="0" Height="30"
                 Value="{Binding ElementName=chkBoxes,Path=Tag }"
                 Maximum="{Binding ElementName=chkBoxes,Path=CbCount }"
    />
</StackPanel>

The really weird thing is if within the DP definition of the CbCount if I change the FrameworkPropertyMetadata to a really small value to say (double)0.001 the problem goes away.

I am running this on XP.

All help gratefully received - thanks.

Update: I have been digging into this again as it gnaws at my sole (who said get a life!)

Things I did:

1) Adding a slider which also like progressBar inherits from RangeBase gives me the expected behaviour.

2) Spinning up reflector I can see the static ctor for ProgressBar sets the default value first to 100, RangeBase.MaximumProperty.OverrideMetadata(typeof(ProgressBar), new FrameworkPropertyMetadata(100.0)); Should AffectMeasure? whereas in the slider: RangeBase.MaximumProperty.OverrideMetadata(typeof(Slider), new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsMeasure));

3) So we need another layout pass after a I set the ProgressBar.Value Going back to my simple POC application if within a the progressBar loaded handler in the client window I jig the layout on the first run through:

this.Width += 1; //trigger another layout pass

Then, hey, presto it works.

So is this a bug?

I still do not fully understand though how the progressBar value which is calculated from Minimum and Maximum values is affected in this way and not the Slider - the default value of Maximum appears to be having an effect and it looks as if the ProgressBar default should affect the measure pass. (missing FrameworkPropertyMetadataOptions.AffectsMeasure.)

Can anyone help, either confirm my thinking or explain what is really happening here?

+1  A: 

ucChkBoxes_Loaded method gets called after the progressbar gets rendered. When the progressbar gets rendered, Tag and CbCount are zero meaning that the progressbar will have min=0, max=0 and value=0, which is correctly drawn as as 100%. If you invalidate the progressbar, for example resize window it will show as 0%, since now Tag and CbCount have been updated.

To fix, don't wait until ucChkBoxes.Loaded() is called to initialize your control, do it in constructor or when initializing the DP for CbCount, for example.

public ucChkBoxes()
{
    InitializeComponent();
    this.Tag = DEFAULT;
    if (this.ourContainer.Children.Count != 0)
    {
        _totalCount = this.ourContainer.Children.Count;
    }
    this.CbCount = (double)_totalCount;
}
Wallstreet Programmer
Thanks for looking at this - yes I realised that I could move this to the ctor and will do this in future and understand the reasoning- what did surprise me though, I was expecting changing the DP CbCount would retrigger the calculation of the Maximum which in turn would internally trigger a new layout pass in the PB as min would be 0 and max would be 5. This seems to be different to how the slider behaves.
Andrew
Looks like Maximum is not meant to be changed after the control is loaded. See this answer: http://stackoverflow.com/questions/655780/progressbar-not-updating-on-change-to-maximum-through-binding/656187
Wallstreet Programmer
Thanks this clears up the mystery and I updated the code. So PB Maximum value gets updated via the DP within the base class RangeBase but PB does nothing with it. SetProgressBarIndicatorLength is never called so the rendering is not updated! This explains why using Snoop and DependencyPropertyHelper.GetValueSource I could see only the Maximum and Value had updated but not its visual and the control ends up in an inconsistent state. Looks like a subtle bug.
Andrew
@Wallstreet Programmer, I have unmarked this for now as the accepted answer as Microsoft are looking into this. I previously gave you an upvote in appreciation for your help. Will keep this thread updated either way.
Andrew
@Wallstreet Programmer - here is the link to the other msdn WPF thread http://social.expression.microsoft.com/Forums/en-US/wpf/thread/86cea199-ecf1-45d8-9448-88c9a6c910b3thanks for your help so far with this.
Andrew