How to resize a WPF element on a static event?
I have a GUI with elements that should be displayed with a specific size in millimeters.
For this purpose I have a (global) static classPPMM
(= pixels per millimeter) with a 开发者_高级运维double? Factor
and an event FactorChanged
, both static. The setter of Factor
calls the FactorChanged
event handler.
The elements in the GUI are UserControls and hook (in constructor) to the FactorChanged
event to update their pixel sizes or ScaleTransforms.
When layouting the GUI, I don't want to create UserControls for every object to be sized in millimeters.
Rather, I'd like to use something in the way of<elem Margin="0, {?:getPixelsFromMillimeters(
desiredSize:{x:Const 20mm},
fallback:{x:Const 80px})}" />
The mm size and the fallback pixel size should be bindable.
I thought about binding to Factor
and using an IValueConverter
with the desired mm size in the parameter
of the converter. But then I can't bind to a variable mm size value.
I could bind to the mm size and use a converter, but then changing Factor
wouldn't update the measure.
I also didn't manage to create a DependencyProperty in a static class (GetValue
and SetValue
are not available there), but that would be a different SO question...
What are proper ways to achieve what I'm trying to do?
Are there details I left unanswered? If yes, please leave a comment.
I came up with a working answer myself from stukselbax' comments...
In short: Some WPF size or other measure property multibinds to the static DependencyProperty Factor and to some static doubles as parameters (size in mm, fallback size in pixels) via an IMultiValueConverter. The static Factor comes from a singleton. There is an additional event FactorChanged in the singleton that other classes can subscribe.
Create a class to use as singleton instance:
public class PPMMSingleton : DependencyObject
{
public double? Factor
{
get { return (double?)GetValue(FactorProperty); }
set { SetValue(FactorProperty, value); }
}
// Using a DependencyProperty as the backing store for Factor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FactorProperty =
DependencyProperty.Register("Factor", typeof(double?), typeof(PPMMSingleton),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.Inherits,
new PropertyChangedCallback(OnFactorChanged),
new CoerceValueCallback(CoerceFactor)));
private static object CoerceFactor(DependencyObject element, object value)
{
return value;
}
public static void OnFactorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
PPMMSingleton ppmm = (PPMMSingleton)sender;
ppmmEventArgs e =
new ppmmEventArgs(
(double?)args.OldValue,
(double?)args.NewValue);
ppmm.OnFactorChanged(e);
}
private void OnFactorChanged(ppmmEventArgs e)
{
if (FactorChanged != null)
FactorChanged(e);
}
public event ppmmEventHandler FactorChanged;
}
Create delegate and EventArgs for FactorChanged:
public delegate void ppmmEventHandler(ppmmEventArgs e);
public class ppmmEventArgs : EventArgs
{
public ppmmEventArgs(double? oldFactor, double? newFactor)
{
OldFactor = oldFactor;
NewFactor = newFactor;
}
public double? OldFactor { get; private set; }
public double? NewFactor { get; private set; }
}
Create a static class to host this singleton:
public static class PPMM
{
public static double? Factor
{
get { return Singleton.Factor; }
set
{
Singleton.Factor = value;
}
}
private static PPMMSingleton _singleton = null;
public static PPMMSingleton Singleton
{
get
{
if (_singleton == null)
_singleton = new PPMMSingleton();
return _singleton;
}
}
public static event ppmmEventHandler FactorChanged
{
add { Singleton.FactorChanged += value; }
remove { Singleton.FactorChanged -= value; }
}
}
Create an IMultiValueConverter that takes the Factor, the size in mm and an optional size in pixels as fallback:
public class FactorAndMillimeterToPixelConverter : IMultiValueConverter
{
#region IMultiValueConverter Member
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((values.Length < 2)
|| !(values[0] is double?))
return Binding.DoNothing;
double? factor = (double?)values[0];
switch(values.Length)
{
case 2:
if(!(values[1] is double))
return Binding.DoNothing;
// values[0]: Factor, values[1]: SizeMM
// if Factor is null, no fallback provided -> donothing
if (!factor.HasValue)
return Binding.DoNothing;
// else return calculated width
return factor.Value * (double)values[1];
case 3:
if (!(values[1] is double) || !(values[2] is double))
return Binding.DoNothing;
// values[0]: Factor, values[1]: SizeMM, values[2]: SizePixelsFallback
// if Factor is null, but fallback provided -> return fallback
if (!factor.HasValue)
return (double)values[2];
// else return calculated width
return factor.Value * (double)values[1];
default:
// value.Length > 3
return Binding.DoNothing;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Put a resource with the converter in your XAML:
<mynamespace:FactorAndMillimeterToPixelConverter x:Key="fm2pconv" />
MultiBind to the static Factor dependency property and to the size in mm and the fallback size in pixels (both as static doubles) in the XAML:
<Border>
<Border.Width>
<MultiBinding Converter="{StaticResource fm2pconv}">
<MultiBinding.Bindings>
<Binding Path="Factor" Source="{x:Static tc:PPMM.Singleton}" />
<Binding>
<Binding.Source>
<sys:Double>50</sys:Double>
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<sys:Double>400</sys:Double>
</Binding.Source>
</Binding>
</MultiBinding.Bindings>
</MultiBinding>
</Border.Width>
<TextBlock
Text="Wenn Factor gesetzt ist, ist dieser Kasten 50mm breit. Ansonsten ist er 400px breit. Seine Width wird beeinflusst."
TextWrapping="WrapWithOverflow"
>
</TextBlock>
</Border>
One could also bind the size in mm to a dependency property (assuming the correct DataContext is set):
<Border.Width>
<MultiBinding Converter="{StaticResource fm2pconv}">
<MultiBinding.Bindings>
<Binding Path="Factor" Source="{x:Static tc:PPMM.Singleton}" />
<Binding Path="WidthInMMInCodeBehind" />
<Binding Path="FallbackWidthInPixelsInCodeBehind" />
</MultiBinding.Bindings>
</MultiBinding>
</Border.Width>
Voilà!
精彩评论