7

我正在使用本文和其他资源慢慢学习 WPF。

我专注于应用程序逻辑 - 定义模型 + 视图模型,并创建对它们进行操作的命令。我还没有看过视图和.xaml格式。

在处理逻辑时,我想要一个可以呈现我绑定到它的任何 viewModel 的视图。视图应该

  • 将任何公共string属性呈现为文本框,并将文本框绑定到属性
  • 将属性名称呈现为标签。
  • 将任何公共“命令”属性呈现为按钮,并将按钮绑定到命令(可能仅当命令不带参数时?)

在维护 MVVM 设计模式的同时,这样的事情可能吗?如果是这样,我将如何实现它?此外,文章建议避免使用.xaml代码隐藏——这个视图可以在纯 xaml 中实现吗?

4

4 回答 4

6

我认为仅在 XAML 中是不可能的。如果您想在运行时生成视图,那么您只需在 ViewModel 上使用反射并相应地生成控件。如果您想在编译时生成视图,那么您可以在构建时使用一些模板引擎(如 T4 或字符串模板)或 CodeDom 从您的 ViewModel 生成 xaml 文件。或者你可以更进一步,使用一些元数据格式(甚至是 DSL),从中生成模型和视图等等。这取决于您的应用程序需求。

而且在 MVVM 代码隐藏中,对于视觉逻辑和绑定到模型/视图模型,这在 XAML 中是无法完成的。

于 2012-04-13T11:33:42.520 回答
3

我现在已经实现了一半,我希望下面的代码可以帮助其他尝试这样做的人。变成一个更强大的库可能会很有趣。

AbstractView.xaml

<UserControl x:Class="MyApplication.View.AbstractView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel Name="container">
    </StackPanel>
</UserControl>

AbstractView.xaml.cs

public partial class AbstractView : UserControl
{
    public AbstractView()
    {
        InitializeComponent();

        DataContextChanged += Changed;
    }

    void Changed(object sender, DependencyPropertyChangedEventArgs e)
    {
        object ob = e.NewValue;
        var props = ob.GetType().GetProperties();

        List<UIElement> uies = new List<UIElement>();
        foreach (var prop in props)
        {
            if (prop.PropertyType == typeof(String))
                uies.Add(makeStringProperty(prop));
            else if (prop.PropertyType == typeof(int))
                uies.Add(makeIntProperty(prop));
            else if (prop.PropertyType == typeof(bool))
                uies.Add(makeBoolProperty(prop));
            else if (prop.PropertyType == typeof(ICommand))
                uies.Add(makeCommandProperty(prop));
            else
            {
            }
        }

        StackPanel st = new StackPanel();
        st.Orientation = Orientation.Horizontal;
        st.HorizontalAlignment = HorizontalAlignment.Center;
        st.Margin = new Thickness(0, 20, 0, 0);
        foreach (var uie in uies) {
            if (uie is Button)
                st.Children.Add(uie);
            else
                container.Children.Add(uie);
        }
        if (st.Children.Count > 0)
            container.Children.Add(st);

    }

    UIElement makeCommandProperty(PropertyInfo prop)
    {
        var btn = new Button();
        btn.Content = prop.Name;

        var bn = new Binding(prop.Name);
        btn.SetBinding(Button.CommandProperty, bn);
        return btn;
    }

    UIElement makeBoolProperty(PropertyInfo prop)
    {
        CheckBox bx = new CheckBox();
        bx.SetBinding(CheckBox.IsCheckedProperty, getBinding(prop));
        if (!prop.CanWrite)
            bx.IsEnabled = false;
        return makeUniformGrid(bx, prop);
    }

    UIElement makeStringProperty(PropertyInfo prop)
    {
        TextBox bx = new TextBox();
        bx.SetBinding(TextBox.TextProperty, getBinding(prop));
        if (!prop.CanWrite)
            bx.IsEnabled = false;

        return makeUniformGrid(bx, prop);
    }

    UIElement makeIntProperty(PropertyInfo prop)
    {
        TextBlock bl = new TextBlock();
        bl.SetBinding(TextBlock.TextProperty, getBinding(prop));

        return makeUniformGrid(bl, prop);
    }

    UIElement makeUniformGrid(UIElement ctrl, PropertyInfo prop)
    {
        Label lb = new Label();
        lb.Content = prop.Name;

        UniformGrid u = new UniformGrid();
        u.Rows = 1;
        u.Columns = 2;
        u.Children.Add(lb);
        u.Children.Add(ctrl);

        return u;
    }

    Binding getBinding(PropertyInfo prop)
    {
        var bn = new Binding(prop.Name);
        if (prop.CanRead && prop.CanWrite)
            bn.Mode = BindingMode.TwoWay;
        else if (prop.CanRead)
            bn.Mode = BindingMode.OneWay;
        else if (prop.CanWrite)
            bn.Mode = BindingMode.OneWayToSource;
        return bn;
    }

}
于 2012-04-13T13:54:03.960 回答
3

我不确定这是否适合用于“纯 MVVM”方法,当然并非所有事情都可以通过绑定来实现。而且我只是放弃了避免在此处为您的“视图”使用代码隐藏的想法,这本质上是一项程序化任务。你应该坚持的一件事是让 ViewModel 不了解视图,这样当你用“真实的东西”替换它时,就没有工作要做了。

但是,这似乎是合理的做法;它几乎听起来更像是一个调试可视化器——您可以利用现有的工具来实现这一点。

(如果您确实想在大多数带有标准ItemsControl和模板的 XAML 中执行此操作,您可能会编写一个转换器以通过反射以某种可以绑定的形式公开您的 ViewModel 的属性,这是一组具有公开元数据的包装器对象,但我认为确保暴露的属性可以正确绑定会比它的价值更多)

于 2012-04-13T11:40:34.827 回答
1

Pointer: Generate a dynamic DataTemplate as a string tied to the specific VM (Target). Parse it via XamlReader. Plonk it into your app resources in code.

Just an idea.. run with it.. Should be done by some type other than the View or the ViewModel.

于 2012-04-13T12:39:17.363 回答