wpf ViewModel Property Binding from another viewmo

2019-09-11 18:30发布

问题:

how do i bind a property declared in a viewmodel to take value from another viewModel?

let me explain

i have 2 ViewModels (Implements INotifyPropertyChanged) and 1 View

InvoiceView ( just my invoice design no matter if is a user control or window or a dataTemplate)

InvoiceViewModel

NoteListingVM (this viewmodel has a property let's name it TableRefID)

In the ViewInvoice i have an expander with it's dataContext set to (NoteListingVM) to show some notes that are linked with the specific invoiceID

I have a problem when i try the following

 <Expander Header="NOTES"  DockPanel.Dock="Top" Foreground="{StaticResource AlternateForeGround}">
                    <DockPanel>
                        <DockPanel.DataContext>
                            <WendBooks:NoteListingVM TableRefID="{Binding InvoiceID}" x:Name="TransactionNotes"></WendBooks:NoteListingVM>
                        </DockPanel.DataContext>

A 'Binding' cannot be set on the 'TableRefID' property of type 'NoteListingVM'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

so as the error said, i cannot use a property.

Then i think to use a DependencyProperty. But DependencyProperty cannot work properly in ViewModels if you implement an InotifyPropertyChanged. ( and this is what most users suggest when you implement a ViewModel - "INotifyPropertychanged")

DependencyPropertys are work well when you have a userControl or CustomControl. But this is not my case(i dont have a usercontrol or customControl i only have a ViewModel that i want to assign / pass "parameter" to the NoteListingViewModel when the InvoiceID changed)

so how i will send the InvoiceID (only xaml) to the NoteListingViewModel to filter and show only notes that are link to the current invoice i have front on me? what is the proper way? i am sure something i missed or misunderstand the mvvm pattern?

回答1:

Don't do it that way. Do it in a viewmodel-centric way instead: Make the NoteListingVM a property of the parent viewmodel.

Instead of structuring your application as a tree of views which own viewmodels that struggle to know what they need to about each other, make it a tree of viewmodels that own their own relationships. Festoon them with views that don't need to know about anything but their own viewmodels.

Note that the InvoiceID property below updates Notes.InvoiceID when it changes. Easy as pie.

public MyViewModel()
{
    Notes = new NoteListingVM();
}

private int _invoiceID = 0;
public int InvoiceID
{
    get { return _invoiceID; }
    set
    {
        if (value != _invoiceID)
        {
            _invoiceID = value;
            Notes.InvoiceID = this.InvoiceID;
            OnPropertyChanged();
        }
    }
}

private NoteListingVM _notes = null;
public NoteListingVM Notes
{
    get { return _notes; }
    protected set
    {
        if (value != _notes)
        {
            _notes = value;
            OnPropertyChanged();
        }
    }
}

XAML. You could wrap a DockPanel around the ContentControl if you want.

<Expander 
    Header="NOTES"  
    DockPanel.Dock="Top" 
    Foreground="{StaticResource AlternateForeGround}"
    >
    <ContentControl Content="{Binding Notes}" />
</Expander>

Alternate

You could also write a NotesView UserControl that has an InvoiceID dependency property, and bind that in the XAML. It would use the same NoteListingVM; you'd assign that via the DataContext property just as you've been doing. The user control's InvoiceID dependency property would have a change handler that updates the viewmodel's InvoiceID, which would let you use a binding to set the InvoiceID. That's a "proper XAML" way to do what you originally had in mind.

You could also entirely rewrite NoteListingVM as a UserControl, but that's more work and I don't see a whole lot of point to it.

You don't mix viewmodel/INotifyPropertyChanged properties with dependency properties in the same class.