So, I have a button that upon clicking will display a ProgressBar with its value bound to the parent element's attached property called ProgressValueProperty.
This is the code I've used for the ProgressBar (this is in a separate xaml file)
<!-- Regular Button -->
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource BaseStyle}">
<Setter Property="Background" Value="{StaticResource WordOrangeBrush}"/>
<Setter Property="Foreground" Value="{StaticResource ForegroundLightBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="{StaticResource FontSizeLarge}"/>
<Setter Property="FontFamily" Value="{StaticResource LatoRegular}"/>
<Setter Property="Padding" Value="50 10"/>
<Setter Property="Margin" Value="0 10"/>
<Setter Property="localap:IsBusyProperty.Value" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Border
x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True"
CornerRadius="10"
>
<Grid>
<!-- Login Text -->
<TextBlock
Text="{TemplateBinding Content}"
Focusable="False"
FontFamily="{TemplateBinding FontFamily}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Visibility="{TemplateBinding localap:IsBusyProperty.Value,Converter={localvc:AttachedPropertyValueConverter}}"
/>
<ProgressBar
Minimum="0"
Maximum="100"
Value="{Binding Path=(localap:ProgressValueProperty.Value),RelativeSource={RelativeSource TemplatedParent},Converter={localvc:StringIntValueConverter}}"
Visibility="{TemplateBinding localap:IsBusyProperty.Value,Converter={localvc:AttachedPropertyValueConverter},ConverterParameter=True}"
>
</ProgressBar>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style> Visibility="{TemplateBinding localap:IsBusyProperty.Value,Converter={localvc:AttachedPropertyValueConverter}}"
/>
<!--<ProgressBar
Minimum="0"
Maximum="100"
Value="{Binding Path=(localap:ProgressValueProperty.Value),RelativeSource={RelativeSource TemplatedParent},Converter={localvc:StringIntValueConverter}}"
Visibility="{TemplateBinding localap:IsBusyProperty.Value,Converter={localvc:AttachedPropertyValueConverter},ConverterParameter=True}"
>
</ProgressBar>-->
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
However, this creates an error during design time Failed to create a 'Path' from the text '(p6:BaseAttachedProperty`2.Value)'.
I tried to bind the value of the ProgressBar using this method
Value="{TemplateBinding localap:ProgressValueProperty.Value,Converter=localvc:StringIntValueConverter}}"
and while this does not create the previous error, runtime does not seem to change the ProgressBar's progress in the UI though creating a breakpoint at the StringIntValueConverter seems to show that the value is being passed in, at least to the converter.
Wondering if anyone knows how a workaround for the above?
Edit :
- Updated the code above for a more complete show of using the progress bar within a button.
- The Button code within the window file itself:
<Button
Content="Next"
localap:IsBusyProperty.Value="{Binding LoginIsRunning}"
Command="{Binding LoginCommand}"
CommandParameter="{Binding ElementName=Page}"
HorizontalAlignment="Center"
localap:ProgressValueProperty.Value="{Binding ProgressValue}"
Tag="50"
>
</Button>
- The attached properties are all classes inheriting from the below base class:
public abstract class BaseAttachedProperty<Parent, Property>
where Parent : BaseAttachedProperty<Parent, Property>, new()
{
public event Action<DependencyObject, DependencyPropertyChangedEventArgs> ValueChanged = (sender, e) =>
{
Debug.WriteLine($"{sender.ToString()} value changed : {e.NewValue}");
};
public static Parent Instance { get; private set; } = new Parent();
public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached("Value", typeof(Property), typeof(BaseAttachedProperty<Parent, Property>), new PropertyMetadata(new PropertyChangedCallback(OnValuePropertyChanged)));
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Call the parent function
Instance.OnValueChanged(d, e);
// Call event listeners
Instance.ValueChanged(d, e);
}
public static Property GetValue(DependencyObject d) {
var p = (Property)d.GetValue(ValueProperty);
Debug.WriteLine($"Getting Value of {d.ToString()} : {Instance.ToString()} - {p.ToString()}");
return p; }
public static void SetValue(DependencyObject d, Property value) {
Debug.WriteLine($"Setting Value of {d.ToString()} : {Instance.ToString()} - {value.ToString()}");
d.SetValue(ValueProperty, value);
}
public virtual void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine($"{sender.ToString()} On value changed : {e.NewValue}");
}
}
- The value converter :
public class StringIntValueConverter : BaseValueConverter<StringIntValueConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return 90;
try
{
return int.Parse(value.ToString());
}
catch
{
return 40;
}
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo cultue)
{
throw new NotImplementedException();
}
}
public abstract class BaseValueConverter<T> : MarkupExtension, IValueConverter
where T:class,new()
{
private static T _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _converter ?? (_converter = new T());
}
public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo cultue);
}
Thanks.