开发者

How to resize a WPF element on a static event?

开发者 https://www.devze.com 2023-03-19 01:19 出处:网络
I have a GUI with elements that should be displayed with a specific size in millimeters. For this purpose I have a (global) static class PPMM (= pixels per millimeter) with a 开发者_高级运维double? Fa

I have a GUI with elements that should be displayed with a specific size in millimeters.

For this purpose I have a (global) static class PPMM (= 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à!

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号