简体   繁体   中英

How to provide a list of custom property names for use by the Visual Studio XAML Designer when writing Bindings?

I have a class, called Model<T> , that implements IDynamicObjectProvider and wraps an object of type T , forwarding property access to an underlying value, while adding extra stuff (eg, Model<T> implements INotifyPropertyChanged whereas T does not).

I use these Model<T> objects as properties of my DataContext in WPF, essentially:

public class Person { public string Name { get; set; } }

public class EditPersonViewModel {
    public Model<Person> Person { get; set; }
    public ICommand SavePersonCommand { get; set; }
}
<StackPanel d:DataContext="{d:DesignInstance vm:EditPersonViewModel}">
    <TextBox Text="{Binding Person.Name}" />
    <Button Command="{Binding SavePersonCommand}" /> 
</StackPanel>

The WPF Binding engine binds to Model<Person> dynamic getters and setters successfully.

Now, I would like to get auto-completion from the Person type's properties in the XAML designer when I write a binding that starts with Person. , in a way that requires as little application code as possible; ideally, it would be entirely implementable by the provider of the library that defines Model<T> . (I am the one that writes the library and I would like my clients to be able to use the Model<T> class with as little fuss as possible).

Here are the ways that I know would work, and my objections to them:

  1. Define a new class, EditPersonViewModelDesign , that would mirror all the EditPersonViewModel properties, and use that as the design instance.

     public class EditPersonViewModelDesign { public Person Person { get; set; } public ICommand SavePersonCommand { get; set; } } 
     <StackPanel d:DataContext="{d:DesignInstance vm:EditPersonViewModelDesign}"> <TextBox Text="{Binding Person.Name}" /> <Button Command="{Binding SavePersonCommand}" /> </StackPanel> 

    While that does not require an undue amount of code, the fact that this class will still be left in the library at runtime unless I ask people to complicate the build (whether they add a project with only these classes, or they add build variables) does not sit altogether well with me. Also, it is easier for the Design and the View model classes to drift apart.

  2. Refine the data context of the inner element and affect a new design instance to it, eg:

     <StackPanel d:DataContext="{d:DesignInstance vm:EditPersonViewModel}"> <TextBox DataContext="{Binding Person}" d:DataContext="{d:DesignInstance m:Person}" Text="{Binding Name}" /> <Button Command="{Binding SavePersonCommand}" /> </StackPanel> 

    While that solution does not require creating a new type, the added XAML code feels pretty invasive to me.

I don't think that there actually is a satisfying solution here, as the only true 'not-workaround' would be a native support of IntelliSense for dynamic binding targets (ie call GetDynamicMemberNames on the DynamicMetaObject returned by GetMetaObject() of the design instance of Model<T> . So, I would recommend a feature request , first of all.

If I were your customer, I would not spend the effort of defining design time bindings everywhere just for IntelliSense support. It requires more thinking to specify the correct bindings than to remember or look up and type the property names.

Here is one idea, which might work. I haven't tested it, of course, but with some research it might get you there. You seem to be knowing, what you're doing:

Have a look at the IsInDesignTool property of the static DesignerProperties class in System.ComponentModel :

DesignerProperties.IsInDesignTool

A kind of if DesignMode do this, else do that approach which can be implemented in the ViewModels is presented here . This could be an approach to return the inner model (of type T), instead of the wrapped model (Model). Not sure whether it would get you anywhere , but it might be worth it to think it through. It's an interesting feature to know about, anyway...

Last idea: Improve the user experience by shipping code snippets and think of an intuitive way to distinguish the design time binding and the runtime binding. Maybe something like:

public abstract class ViewModelBase<T>
{
    public Model<T> Model { get; }

    // Maybe use compiler directives to throw exception if requested at runtime
    public T DesignModel { get; }
}

Code snippet which creates

d:Text="{Binding DesignModel.Name}" Text="{Binding Model.Name}"

Don't know, how powerful VS code snippets are, because I'm using ReSharper, but with ReSharper, you would make the DP of the control a variable $ControlProp$ , the property name a variable $PropName$ . Then IntelliSense would work on the design time binding and would automatically copy the property name to the second binding, if you hit enter.

You would enter some shortcut pbind , then Text , Enter, N , which would fire IntelliSense, select Name , Enter. Done. Would be easy, at least.

Bottomline: I don't have a plug and play answer to your question, but my points might be good for some inspiration... At least I hope so.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM