WPF游戏编辑器 - 在UI/代码中更新属性

3
我正在尝试在C# / WPF中创建游戏编辑器。编辑器由一个用户控件组成,显示场景(使用SharpGL渲染OpenGL),以及许多用于编辑当前场景和对象的控件。对象由组件组成,其属性可以在编辑器中进行编辑(类似于Unity游戏引擎)。我已经拥有“组件编辑器”视图,该视图使用反射查找组件上的所有属性,并为每个属性创建属性编辑器(例如,滑块)。然而,我不知道如何在UI和代码之间绑定属性。
问题是,我希望这些属性在代码中更改时能够在UI中更新,以及在UI中更改时能够在代码中更新。如果我想将编辑器控件(例如更改属性的滑块)绑定到组件属性,则它们必须实现NotifyPropertyChanged,这将相当麻烦。我猜另一种方式是脏检查,但我不确定这是否是一个好主意。
有人能给我指点如何处理UI / Code之间的属性更新吗?我希望它的工作方式与Unity中的工作方式基本相同,即您无需在组件类中编写任何额外的东西即可使属性可编辑。
编辑:为了更清楚地说明我正在尝试实现和已经拥有的功能,这里是“组件编辑器”用户控件的一部分。它的数据上下文是一个组件实例(模型)。PropertiesConverter返回它的属性(通过component.GetType().GetProperties())。ComponentPropertyTemplateSelector会在属性编辑器用户控件上进行决策(例如,对于双精度属性,它将选择具有用于编辑值的文本框的“数字编辑器”)。我感兴趣解决的问题是如何将组件的属性与编辑器控件双向绑定。
<ItemsControl x:Name="ComponentProperties" Grid.Row="1" ItemTemplateSelector="{StaticResource ComponentPropertyTemplateSelector}">
    <ItemsControl.ItemsSource>
        <Binding Converter="{StaticResource PropertiesConverter}"/>
    </ItemsControl.ItemsSource>
</ItemsControl>

请注意,INotifyPropertyChanged 是为此类事情而设计的,并且实际上在 .NET 中早于 WPF 和 MVVM。http://www.codeproject.com/Articles/87715/Native-WPF-PropertyGrid。 - user585968
2个回答

3
我认为你可能希望遵循MVVM模式,它使用了INotifyPropertyChanged接口。如果你在Google上搜索MVVM,会立刻出现一些好的文章。已经有一些现成的工具可帮助你入门。根据你在问题中描述的情况,MVVM模式基本上是这样工作的。它解耦了UI和代码,但仍保持连接。简单来说,你需要在一个类上实现INotifyPropertyChanged,然后将该类的一个实例设置为你想要设置绑定的控件的DataContext。最好看一个示例:

Xaml:

   <StackPanel>
        <Slider Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBox Text="{Binding MyText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBox  Text="{Binding MyText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <Slider Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    </StackPanel>

我创建了一个视图模型基类,以节省一些代码编写:

class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

An example view model class:

 class MyViewModel : ViewModelBase
    {

        private int sliderValue;
        private string myText;

        public int SliderValue
        {
            get { return this.sliderValue; }
            set
            {
                this.sliderValue = value;
                this.OnPropertyChanged();
            }
        }


        public string MyText
        {
            get { return this.myText; }
            set
            {
                this.myText = value;
                this.OnPropertyChanged();
            }
        }

    }

如何连接绑定(在这种情况下是控件的代码后台):
  public MainWindow()
        {
            InitializeComponent();

            this.DataContext = new MyViewModel();
        }

正如你所看到的,设置视图模型和XAML需要一些工作。与其他解决方案相比,我认为这个方案需要投入的“工作”量还是相对较少的。但我不知道是否有什么方法可以绕过它并让它像“魔法”一样工作。值得一提的是,可以查看一下现有的MVVM工具,可能会有一些可以使事情变得更加简单的东西。


INPC的完美示例。您还可以在任何子控件中使用DependecyProperties,这使您能够在设计时在属性编辑器中查看属性。这是一种非常酷的方式,可以快速掌控您的设计表面。我更喜欢DPs而不是INPC,因为它们在功能上更加“丰富”。 - JWP
@JohnPeters 不完全确定为什么有人会将依赖属性放入_view model_中。http://stackoverflow.com/questions/1500669/can-somone-give-example-of-dependency-property-in-viewmodel 和 https://dev59.com/ZXVC5IYBdhLWcg3wcgud - user585968
你说得对,除非Viewmodel从DepenecyObject继承,否则它确实无法完成。在视图模型中这是没有意义的,因为它们从未在设计时用于连接属性值。我并没有暗示其他,因为我提到了子控件“用户控件”在代码后台中拥有它们。依赖属性的优势是您可以获得设计时属性暴露,这是使用INPC不可能实现的。 - JWP
@JohnPeters 同意,但在写作时,MisterXero的代码没有控制代码源发布任何属性,因此我不清楚您为什么提到DP。干杯 - user585968
我喜欢在需要时向人们介绍一些不为人知的、离题但具有高价值的东西。谢谢! - JWP
@MisterXero 如果我理解正确的话,我需要为每个不同的模型(组件)手动创建一个视图模型?我的目标是创建一个视图,它由自动生成的属性编辑器(滑块等)组成,这些属性编辑器对应组件中的每个字段/属性(通过component.GetType().GetProperties()检索)。然而,我不确定如何为每个属性生成双向绑定,使其与其编辑器控件和模型之间保持同步。 - CodeCommander

1
您可以使用Castle Dynamic Proxy(将类包装在代理中)或Fody(在后期构建步骤中修改IL)自动添加IPropertyChangeNotification支持。

是的,INPC 是一个不错的选择,只要记得设置数据上下文和/或项源值。您可以绑定到复杂类并在其中设置各个属性,这可以触发 INPC。这让您拥有了一个大的“游戏场”,但请记住,“优先使用组合而非继承”,换句话说,尽量包含类和属性,而不是继承它们。 - JWP
这似乎是一个有趣的解决方案,因为它不需要手动将INPC添加到组件的属性中(这正是我想要的),但仍然具有该功能。我想我必须试一试。 - CodeCommander
如果您正在使用像EF或NHibernate这样的ORM,请小心。如果在将成员加载到存储库后替换为代理,则ORM会认为您已更改它并将其序列化,这将导致性能问题。大多数ORM都可以在创建时注入代理以避免此问题。 - Mark Feldman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接