MVVM Main window control bind from child user cont

2019-01-27 01:46发布

I’m very new to MVVM and getting my first POC done now. However, I have been hitting my head to get one issue resolved for 2 days. Thought of explaining to you guys may help and resolve the issue so quickly. Let me brief my issue now. I have WPF MVVM application with a main View bound to MainViewModel. I have Textblock here to bind some content from the viewmodel while loading the screen which is working awesome. I also have ChildUserControl bound to ChildViewModel. Now, I need to bind different content to the Textblock in the main window from user control up on some action which is taking place in user control level. How it is possible ?

here is the sample code I have MainWindow.Xaml

<Window.Resources>
    <viewModel:MainViewModel x:Key="mainWindowViewModel"/></Window.Resources>

<TextBlock Name="txtStatus" Text="{Binding StatusMessage, Mode=OneWay }"/>

ChildUserControl.xaml

<UserControl.Resources>
    <viewModel:ChildModelView x:Key="ChildModelView"/> </UserControl.Resources>

public class ChildModelView : BaseViewModel
{
// Some child level logic..
// then need to update the txtStatus text block from parent
}

Your helps are much appreciated..!

2条回答
劳资没心,怎么记你
2楼-- · 2019-01-27 02:44

I may be misunderstanding your needs here, but it sounds like you have a TextBlock in the MainWindow that needs to be updated in response to something that occurs in and with data supplied by the ChildWindow. Assuming that's what you're trying to do then I think there are a few different ways that you could do it. In particular, you could use an attached event.

http://msdn.microsoft.com/en-us/library/bb613550.aspx

The technical explanation is a bit lengthy, so I'll leave it to MSDN to do that, but the short explanation is simply that it's just an event that will be raised from your child window and handled by your MainWindow. The bonus here is that it will pass in your ChildWindowViewModel as the DataContext.

查看更多
做自己的国王
3楼-- · 2019-01-27 02:49

An easy way to achieve this would be to use IoC. When you create your child view model, pass a reference of your main view model to your child viewmodel , and hold it as a private read only variable. You now access all of your main VM publics .

An alternative solution would ppossibly be to use the Mediator pattern.

Knocked up a simple App to demonstrate IoC solution.

App.xaml.cs

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var window = new MainWindow() {DataContext = new MainWindowViewModel() };
        window.Show();
    }
}

MainWindowViewModel.cs

public class MainWindowViewModel : ViewModelBase
{
    private string _statusMessage;

    public string StatusMessage
    {
        get { return _statusMessage; }
        set { _statusMessage = value; this.OnPropertyChanged("StatusMessage"); }
    }

    public ICommand OpenChildCommand { get; private set; }

    public MainWindowViewModel()
    {
        this.StatusMessage = "No status";
        this.OpenChildCommand = new DelegateCommand((o) => this.OpenChild());
    }

    private void OpenChild()
    {
        var window = new ChildWindow {DataContext = new ChildWindowViewModel(this)};
        window.Show();
    }
}

ChildWindowViewModel.cs

public class ChildWindowViewModel : ViewModelBase
{
    private readonly MainWindowViewModel _mainvm;

    public ChildWindowViewModel(MainWindowViewModel mainvm)
    {
        _mainvm = mainvm;
        this.UpdateStatusCommand = new DelegateCommand((o) => this.UpdateStatus());
    }

    public ICommand UpdateStatusCommand { get; private set; }

    private void UpdateStatus()
    {
        this._mainvm.StatusMessage = "New Status";
    }
}

ViewModelBase.cs

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }
}

DelegateCommand.cs

public class DelegateCommand : ICommand
{
    /// <summary>
    /// Action to be performed when this command is executed
    /// </summary>
    private Action<object> executionAction;

    /// <summary>
    /// Predicate to determine if the command is valid for execution
    /// </summary>
    private Predicate<object> canExecutePredicate;

    /// <summary>
    /// Initializes a new instance of the DelegateCommand class.
    /// The command will always be valid for execution.
    /// </summary>
    /// <param name="execute">The delegate to call on execution</param>
    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Initializes a new instance of the DelegateCommand class.
    /// </summary>
    /// <param name="execute">The delegate to call on execution</param>
    /// <param name="canExecute">The predicate to determine if command is valid for execution</param>
    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
        {
            throw new ArgumentNullException("execute");
        }

        this.executionAction = execute;
        this.canExecutePredicate = canExecute;
    }

    /// <summary>
    /// Raised when CanExecute is changed
    /// </summary>
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    /// <summary>
    /// Executes the delegate backing this DelegateCommand
    /// </summary>
    /// <param name="parameter">parameter to pass to predicate</param>
    /// <returns>True if command is valid for execution</returns>
    public bool CanExecute(object parameter)
    {
        return this.canExecutePredicate == null ? true : this.canExecutePredicate(parameter);
    }

    /// <summary>
    /// Executes the delegate backing this DelegateCommand
    /// </summary>
    /// <param name="parameter">parameter to pass to delegate</param>
    /// <exception cref="InvalidOperationException">Thrown if CanExecute returns false</exception>
    public void Execute(object parameter)
    {
        if (!this.CanExecute(parameter))
        {
            throw new InvalidOperationException("The command is not valid for execution, check the CanExecute method before attempting to execute.");
        }
        this.executionAction(parameter);
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <TextBlock Text="{Binding StatusMessage, Mode=OneWay}" />
    <Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="Open Child Window" 
            Command="{Binding Path=OpenChildCommand}"/>
</StackPanel>

ChildWindow.xaml

<Window x:Class="WpfApplication2.ChildWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ChildWindow" Height="300" Width="300">
<Grid>
    <Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="UpdateStatus"
            Command="{Binding Path=UpdateStatusCommand}" />
</Grid>

Image before clicking on update status

Before

Image after clicking on updatestatus

enter image description here

查看更多
登录 后发表回答