Is this WPF ProgressBar Odd render behaviour a Bug?
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 confir开发者_如何学运维m my thinking or explain what is really happening here?
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;
}
精彩评论