ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

ProgressBar and OpacityMask

25 Aug 2015, 17:52

Hi,

I'm trying to customize a ProgressBar, but I have two use-cases that are a bit complicated to me:
  • Segmented circular bar
I've seen some topics in that forum (one about Cooldown Circular Path for instance) that might answer the question but I'm afraid I could not have the segmentation done properly using that method.

To answer both these use-cases, I've thought my template could be as follow:
<ControlTemplate>
    <Grid>
        <Rectangle x:Name="PART_Indicator" Fill="Pink" HorizontalAlignment="Left"/>
        <Border x:Name="PART_Track" Background="Black"/>
        
        <Grid>
            <Grid.OpacityMask>
                <VisualBrush Visual="{Binding ElementName=PART_Indicator}"/>
            </Grid.OpacityMask>
            <Border x:Name="FullTrack" Background="White"/>
        </Grid>
    </Grid>
</ControlTemplate>
The idea is that "PART_Track" represents the empty bar and "FullTrack" represents the bar completely full, both filling the parent Grid. My idea is to use "PART_Indicator" as an OpacityMask, so that I only display the proper proportion of the filled bar.

I can't get it to work on Blend. The bar is completely white, except if the ProgressBar::Value is set to 0 (it becomes fully black). I've tried several combinations but that's the closest I got.
Then, when I use that Template with the XamlPlayer, the application crashes. Here is the content of the file in question (can't upload it directly apparently):
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Background="Gray" Orientation="Vertical" VerticalAlignment="Center">

    <Slider x:Name="PART_Slider" Orientation="Horizontal" Height="22" Maximum="100" Value="4" Width="300"/>

        <ProgressBar Height="29.532"  Value="{Binding Value, ElementName=PART_Slider}" Width="300">

            <ProgressBar.Style>
                <Style TargetType="{x:Type ProgressBar}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate>

                                <Grid>
                                    <Rectangle x:Name="PART_Indicator" Fill="Pink" HorizontalAlignment="Left"/>
                                    <Border x:Name="PART_Track" Background="Black"/>
                                    <Grid>
                                        <Grid.OpacityMask>
                                            <VisualBrush Visual="{Binding ElementName=PART_Indicator}"/>
                                        </Grid.OpacityMask>
                                        <Rectangle Fill="White"/>
                                    </Grid>
                                </Grid>

                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ProgressBar.Style>

        </ProgressBar>

</StackPanel>
Apparently, it has to do with the OpacityMask, and particularly VisualBrush (I've tried to use a LinearGradientBrush with success elsewhere). Do you see what might go wrong?

Obviously, that would only solve my first use case, but I think I could solve the second use-case with the same kind of operation (using Circle arcs as OpacityMask rather than a simple Rectangle). What do you think?

Thank you,
Alexandre.

Edit:
I confirm there's something about VisualBrush that Noesis does not like. I've made a simpler sample to use them and both my application and the XamlPlay crash when I try using these. That's an important feature to me, what else should I use?
Last edited by ZanAlex on 04 Sep 2015, 11:30, edited 1 time in total.
 
User avatar
sfernandez
Site Admin
Posts: 3184
Joined: 22 Dec 2011, 19:20

Re: ProgressBar and OpacityMask

26 Aug 2015, 13:51

Hi Alex,

To accomplish the star rating bar you just need to play with the Clip property ;)
<ProgressBar Width="300" VerticalAlignment="Center" Value="51" BorderBrush="#FF905101" Foreground="#FFFF9E25">
    <ProgressBar.Template>
        <ControlTemplate TargetType="ProgressBar">
            <ControlTemplate.Resources>
                <Geometry x:Key="Stars">
                    M16.5,1L20.2,12 32,12 22.6,19 26.5,31 16.5,23.6 6.5,31 10.4,19 1,12 12.8,12z
                    M53.5000648498535,1L57.2000648498535,12 69.0000648498535,12 59.6000648498535,19 63.5000648498535,31 53.5000648498535,23.6 43.5000648498535,31 47.4000648498535,19 38.0000648498535,12 49.8000648498535,12z
                    M90.500129699707,1L94.200129699707,12 106.000129699707,12 96.600129699707,19 100.500129699707,31 90.500129699707,23.6 80.500129699707,31 84.400129699707,19 75.000129699707,12 86.800129699707,12z
                    M127.500194549561,1L131.200194549561,12 143.000194549561,12 133.600194549561,19 137.500194549561,31 127.500194549561,23.6 117.500194549561,31 121.400194549561,19 112.000194549561,12 123.800194549561,12z
                    M164.500259399414,1L168.200259399414,12 180.000259399414,12 170.600259399414,19 174.500259399414,31 164.500259399414,23.6 154.500259399414,31 158.400259399414,19 149.000259399414,12 160.800259399414,12z
                </Geometry>
            </ControlTemplate.Resources>
            <Viewbox>
                <Grid>
                    <Border x:Name="PART_Track"/>
                    <Border x:Name="PART_Indicator" HorizontalAlignment="Left"
                        Background="{TemplateBinding Foreground}" Clip="{StaticResource Stars}"/>
                    <Path Data="{StaticResource Stars}" Stretch="Fill"
                        Stroke="{TemplateBinding BorderBrush}" StrokeThickness="2" StrokeLineJoin="Round"/>
                </Grid>
            </Viewbox>
        </ControlTemplate>
    </ProgressBar.Template>
</ProgressBar> 
VisualBrush on the other side has some limitations. We only support it when VisualBrush.Visual binds to a named element in the tree, it doesn't work yet when you define the visual tree in the Visual property.

Anyway, if you get a crash using a VisualBrush, please create a ticket in our bugtracker so we can manage this situation and provide a clean message.
 
ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: ProgressBar and OpacityMask

26 Aug 2015, 14:16

Hi,

Thank you for your answer!

I should have asked you before, I'll try that solution a bit later today. Thank you!

I'm not sure I understand what you mean by bound to a named element in the tree. Isn't it what I've tried to do in my sample?

Many thanks
 
User avatar
sfernandez
Site Admin
Posts: 3184
Joined: 22 Dec 2011, 19:20

Re: ProgressBar and OpacityMask

26 Aug 2015, 14:55

If this is crashing, this is not a known bug:
<VisualBrush Visual="{Binding ElementName=PART_Indicator}"/> 
Could you please report it in the bugtracker? Thanks.
 
ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: ProgressBar and OpacityMask

01 Sep 2015, 11:59

Hi,

To achieve my second goal, I think I'll have to create a new Control, deriving from RangeBase that will use the Value property to create the proper arc and use it as a Clip for the PART_Indicator Border.

I was wondering if there was something I should be careful with in the RangeBase class. I'll override OnTemplateChanged (to retrieve the Track/Indicator and add my clipping arc to the Clip property of both) and OnValueChanged (to update my clipping arc). Should I call these in my override?

Many thanks

EDIT:
I've managed to get a nice result by applying what I've described earlier in that post. However, I still have a question.
What I'm doing is:
- Getting PART_Indicator
- Getting its Clip
- Creating a CombinedGeometry containing PART_Indicator's Clip and the arc I'm computing when the Value changes
- Using this CombinedGeometry as a PART_Indicator's Clip

If everything worked as I expected, I should have to simply update the arc as the value changes and PART_Indicator's Clip should be updated as expected. But what happens is that the CombinedGeometry does not change while I'm updating my arc. Everytime the Value changes, I have to create another CombinedGeometry and apply it again to PART_Indicator. That feels really ineffective. Is it an expected behaviour? I've tried to keep the same CombinedGeometry, change one of its geometry with SetGeometry1/2 and apply that result to PART_Indicator but that does not change anything either.
 
User avatar
sfernandez
Site Admin
Posts: 3184
Joined: 22 Dec 2011, 19:20

Re: ProgressBar and OpacityMask

03 Sep 2015, 23:32

I confirmed that we have a bug with the CombinedGeometry, and changes in the Geometry1 or Geometry2 objects are not correctly notified, so render is not updated.

Anyway, CombinedGeometry is not fully implemented, and in case you want to use a geometry intersection, I suggest you to change the template to apply the clip geometries to two nested borders (the desired effect will be the same):
...
<Border x:Name="PART_Indicator">
  <Border Background="{TemplateBinding Foreground}" Clip="{StaticResource segmentsGeometry}"/>
</Border>
...
The inner border, will be clipped with the geometry of the segments. That geometry won't change.

The outer border, PART_Indicator, will be clipped with the calculated arc geometry. That geometry will change as RangeBase.Value changes. You can use a StreamGeometry for this purpose, and OnValueChanged you will only update the contents as follows:
void MyCircularProgressBar::OnValueChanged(NsFloat32 oldValue, NsFloat32 newValue)
{
  // assuming you cached PART_Indicator clip geometry in the OnTemplateChanged()
  StreamGeometryContext context = _indicatorClipGeometry->Open();
  context.BeginFigure(...);
  ...
  context.Close();
}
Is this what you need?
 
ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: ProgressBar and OpacityMask

04 Sep 2015, 09:48

That's sounds like a better idea than what I have at the moment. I will change it accordingly, thanks a lot!

EDIT: It does work and make the code cleaner. Thanks!

Who is online

Users browsing this forum: Ahrefs [Bot], Semrush [Bot] and 1 guest