从单个绑定设置多个样式属性(将一个键解复用到多个资源?)

时间:2016-05-06 14:21:49

标签: c# wpf mvvm

场景:作为一名嵌入式软件工程师,我希望我的MVVM视图能够在我的XAML样式中设置多个属性,从单个绑定到我的ViewModel。

鉴于:我的视图包含一个应用于Button的样式,而我的ViewModel有一个'键'属性。

ButtonStyle.xaml ResourceDictionary的相关位:

<!-- This is the style used for Buttons -->
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
    <!-- Other, common Style setters live here -->
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border x:Name="ButtonBorder">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Image Grid.Column="0"
                               Source="{Binding Path=Key, 
                                        Converter={r:ButtonImageConverter}}"/>
                        <Label Grid.Column="1"
                               Content="{Binding Path=Key,
                                         Converter={r:ButtonTextConverter}}"/>
                    </Grid>
                </Border>
                <!-- ControlTemplate.Triggers and other stuff -->
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

CommandViewModel.cs类的相关位:

// The relevant bits of the ViewModel class that the button is bound to
public class CommandViewModel: INotifyPropertyChanged, ICommand
{
    private String Key;
    {
        get { return key; }
        set { OnPropertyChanged(ref key, value); }
    }

    // ... Plus implementation of OnPropertyChanged, ICommand, etc.
}

时间:按钮绑定到ViewModel对象。

从ViewModel到Button的绑定:

<Button Style="{DynamicResource ButtonStyle}" 
    DataContext="{Binding Path=CloseCommand}"
    Content="{Binding Path=Key}" 
    Command="{Binding Path=Command}" 
    CommandParameter="{Binding}"/>

然后:根据与&#39;键相关联的资源集合设置了几个样式属性(使用值转换器)。属性。

其中一个价值转换器的执行不完整(另一个非常相似,此处未包含):

public class ButtonTextConverter : MarkupExtension, IValueConverter
{
    // This is just so that I can reference it in the XAML as:
    // Converter={r:ButtonImageConverter}
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        String index = value as String;
        if (String.IsNullOrEmpty(index))
        {
            throw new ArgumentException("The name to convert cannot be Null or Empty", "value");
        }

        // The code gets here just fine, with index containing the same
        // string as the CommandViewModel.Key property.
        // But ...
        // How can I find a the String Resouce, using the index, when
        // there may be several all related to the same Control?  I.e.
        // the ButtonImageConverter will also find a String containing
        // the URI path for the image to display on the Button.
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return String.Empty;
    }
}

在架构上,我的View是一个程序集,它包含我的所有XAML(任何XAML组件都没有代码)和资源。 ViewModel位于不同的程序集中,对资源,图像,本地化文本,文化等一无所知。

一个会破坏我的架构的解决方案是向ViewModel类添加更多属性,并让它从XML文件中提取各种值。我不喜欢这样,因为我的模型和ViewModel都受到View内容的污染。我想在视图中保留所有这些内容。

我使用自定义资源类查看了另一个解决方案。 ResourceManager.GetObject Method页面包含一些示例代码,用于创建包含自定义资源的.RESX资源文件。我已经尝试过了,但结果超出了我目前的理解范围,而且我的客户最大利益是尝试使用Microsoft ResX架构。但是,这可能是要走的路,如果有一种简单的方法可以将复杂对象(值转换器可以用来将Key解复用)保存为单个资源实体。

我有哪些选项可以允许不同的资源通过相同的标识&#39;唯一标识,具体取决于哪个值转换器尝试查找资源?

1 个答案:

答案 0 :(得分:0)

我实现的解决方案有点粗糙,并且依赖于为资源提供不同的名称,这些名称是通过将属性名称附加到资源“Key”名称而形成的。我已将其封装到ButtonResources类中,ValueConverter类用作帮助程序来获取实际资源。

internal class ButtonResources
{
    #region Static Fields

    private static ResourceManager resourceManager = 
        new ResourceManager("MyApplication.Resources.ButtonResources",
                            typeof(ButtonResources).Assembly);

    private static Dictionary<String,ButtonResources> resourceDictionary =
        new Dictionary<String, ButtonResources>();

    #endregion

    #region Instance Properties

    internal String Text { get; set; }
    internal BitmapImage Image { get; set; }
    internal SolidColorBrush Brush { get; set; }

    #endregion

    #region Instance Fields

    private String Name { get; set; }

    #endregion

    private ButtonResources(String buttonName)
    {
        this.Name = buttonName;
    }

    private void LoadResources(CultureInfo culture)
    {
        // Load the button Text 
        Text = resourceManager.GetString(Name + "Text", culture);

        // Load the image file from a URI.
        // See https://msdn.microsoft.com/en-us/library/aa970069(v=vs.100).aspx
        // (It's regrettably complex, but there are lots of examples on t'interweb.)
        String imageName = resourceManager.GetString(Name + "Image", culture);
        Image = new BitmapImage(new Uri(@"pack://application:,,,/"
            + Assembly.GetExecutingAssembly().GetName().Name
            + ";component/Resources/"
            + imageName, UriKind.Absolute));

        // Convert the colour resource to a suitable Brush object.
        String colourName = resourceManager.GetString(Name + "Colour", culture);
        Brush = new BrushConverter().ConvertFromString(colourName) as SolidColorBrush;
    }

    internal static ButtonResources FindButton(String buttonName, CultureInfo culture)
    {
        if (String.IsNullOrEmpty(buttonName))
        {
            throw new ArgumentException("The name of the button cannot be Null or Empty",
                                        "buttonName");
        }

        ButtonResources buttonResources;

        // Check whether the requested resource has been loaded yet.
        if (resourceDictionary.ContainsKey(buttonName))
        {
            buttonResources = resourceDictionary[buttonName];
        }
        else
        {
            // Create a new instance with the requested name.
            buttonResources = new ButtonResources(buttonName);

            // Load the object's properties from the Resources.
            buttonResources.LoadResources(culture);

            // Add the new object to the dictionary.
            resourceDictionary.Add(buttonName, buttonResources);
        }

        return buttonResources;
    }
}

每个ValueConverter类都有所简化:

public abstract class ValueConverter
    : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public virtual object ConvertBack(object value, Type targetType,
                                      object parameter, CultureInfo culture)
    {
        return String.Empty;
    }
}

public class ButtonTextConverter : ValueConverter, IValueConverter
{
    public object Convert(object value, Type targetType,
                          object parameter, CultureInfo culture)
    {
        // Use the ButtonResources helper to find the text to place on the button.
        return ButtonResources.FindButton(value as String, culture).Text;
    }
}

public class ButtonImageConverter : ValueConverter, IValueConverter
{
    public object Convert(object value, Type targetType,
                          object parameter, CultureInfo culture)
    {
        // Use the ButtonResources helper to find the image to put on the button.
        return ButtonResources.FindButton(value as String, culture).Image;
    }
}

public class ButtonColourConverter : ValueConverter, IValueConverter
{
    public object Convert(object value, Type targetType,
                          object parameter, CultureInfo culture)
    {
        // Use the ButtonResources helper to find the brush to use for the button.
        return ButtonResources.FindButton(value as String, culture).Brush;
    }
}

注意:在解决方案资源管理器中,我清除了Custom Tool文件的ButtonResources.resx字段。这是因为ButtonResources类处理resgen.exe工具创建的代码所做的一切。

如果我需要本地化资源,我很确定CultureInfo的东西会出错,但它现在有用,所以如果有问题我会解决这个问题。

除了本地化问题之外,ButtonResources类可能会使用一些额外的代码来处理不存在命名资源的情况,从而提供可接受的默认值,或者至少是{ {1}}。同样,这不会导致问题,所以我在这里没有做任何修复。