简体   繁体   中英

WPF Bind different usercontrols to different viewmodels

I am a newbie in WPF, I have a problem concern binding two different ViewModels to two UserControls that will be attached to two Tabpages in a Tabcontrol.

My code snippets are as follows:

MainWindow.xaml

<Window.Resources>
    <local:UserControl1Model x:Key="Control1Model" />
    <local:UserControl2Model x:Key="Control2Model" />
</Window.Resources>

<Grid HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="592">
    <Grid HorizontalAlignment="Left" Height="45" Margin="0,330,-1,-45" VerticalAlignment="Top" Width="593">
        <Button Content="Button" HorizontalAlignment="Left" Margin="490,5,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
    </Grid>
    <TabControl HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="592" >
        <TabItem x:Name="UserControl1TabItem" Header="User Control 1" >
            <Grid x:Name="UserControl1Tabpage" Background="#FFE5E5E5" Margin="0,0,-4,-2" Height="300" VerticalAlignment="Top" IsEnabled="true" >
                <local:UserControl1 VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Control1Model}}" />
            </Grid>
        </TabItem>
        <TabItem x:Name="UserControl2TabItem" Header="User Control 2">
            <Grid x:Name="UserControl2Tabpage" Background="#FFE5E5E5">
                <local:UserControl2 VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Control2Model}}" />
            </Grid>
        </TabItem>
    </TabControl>
</Grid>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    private UserControl1Model _userControl1Model = new UserControl1Model();
    private UserControl2Model _userControl2Model = new UserControl2Model();

    public MainWindow()
    {
        InitializeComponent();

        _userControl1Model.Message = "Hello";
        _userControl2Model.Message = "Test";        
    }
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // Will do something
    }
}

UserControl1Model.cs

public class UserControl1Model : INotifyPropertyChanged
{
    private string _message;

    public string Message
    {
        get { return _message; }
        set
        {
            _message = value;
            OnPropertyChanged("Message");
        }
    }

    public UserControl1Model()
    {
    }

    // Create the OnPropertyChanged method to raise the event
    protected void OnPropertyChanged(string message)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(message));
        }
    }

    #region INotifyPropertyChanged Members

    // Declare the event
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

For trying purpose, the content of UserControl2Model.cs is as same as UserControl1Model.cs

UserControl1.xaml

<UserControl.Resources>
    <app:UserControl1Model x:Key="Control1Model" />
</UserControl.Resources>

<Grid Margin="0,0,0,42" DataContext="{Binding Source={StaticResource Control1Model}}"> 
    <Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>

    <TextBox x:Name="Conrol1ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap" 
       Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
       VerticalAlignment="Top" Width="466" />
</Grid>

UserControl1.xaml.cs

public partial class UserControl1 : UserControl
{        
    public UserControl1()
    {
        InitializeComponent();
    }
}

UserControl2.xaml

<UserControl.Resources>
    <app:UserControl2Model x:Key="Control2Model" />
</UserControl.Resources>

<Grid Margin="0,0,0,42" DataContext="{Binding Source={StaticResource Control2Model}}"> 
    <Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>

    <TextBox x:Name="Conrol2ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap" 
       Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
       VerticalAlignment="Top" Width="466" />
</Grid>

For trying purpose, the content of UserControl2.xaml.cs is as same as UserControl1.xaml.cs

My problem is the initial values, "Hello" and "Test" for the two user controls, which are initialized in MainWindow.xaml.cs cannot be "binded" into the user controls textboxes. What am I doing wrong or missing?

When you declare resources like this

<Window.Resources>
    <local:UserControl1Model x:Key="Control1Model" />
    <local:UserControl2Model x:Key="Control2Model" />
</Window.Resources>

You are actually constructing new instances of UserControl1Model and UserControl2Model instead using the ones you declared in MainWindow.cs

Also you are not creating any ViewModel for the MainWindow. You should create a MainWindowViewModel like such

public class MainWindowViewModel : INotifyPropertyChanged
{
    public ViewModelLocator
    {
        this.FirstModel= new UserControl1Model
        {
            Message = "Hello";
        }

        this.SecondModel = new UserControl2Model
        {
            Message = "Test";
        }
    }

    private UserControl1Model firstModel
    public UserControl1Model FirstModel
    {
        get 
        {
            return this.firstModel;
        }

        set
        {
            this.firstModel= value;
            OnPropertyChanged("FirstModel");
        }
    }

    // Same for the UserControl2Model

    // implementation of the INotifyPropertyChanged
}

Also you would need to set the DataContext for the MainWindow.

public MainWindow()
{
    InitializeComponent();

    this.DataContext = new MainWindowViewModel();     
}

And remove the resources from the UserControl xamls. You are already defining the DataContext in the MainWindow.xaml but the binding should be bound from the MainWindowViewModel as such.

<local:UserControl1 VerticalAlignment="Top" DataContext="{Binding FirstModel}" />

Create a ViewModelLocator. you can find various sites on the internet for this subject.

a simple one would be:

public class ViewModelLocator
{
  public UserControl1Model UserControl1Model { get; set; } = new UserControl1Model();
  public UserControl2Model UserControl2Model { get; set; } = new UserControl2Model();
  public ViewModelLocator
  {
    UserControl1Model.Message = "Hello";
    UserControl2Model.Message = "Test";  
  }
}

then you can use it in your views

<UserControl.Resources>
    <app:ViewModelLocator x:Key="ViewModelLocator" />
</UserControl.Resources>

<Grid Margin="0,0,0,42" DataContext="{Binding UserControl2Model Source={StaticResource ViewModelLocator}}"> 
    <Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>

    <TextBox x:Name="Conrol2ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap" 
       Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
       VerticalAlignment="Top" Width="466" />
</Grid>

Suppose, I create this instance in the xaml as follows: <Window.Resources> <local:MainWindowViewModel x:Key="MainWindowViewModel" /> </Window.Resources> instead of creating inside MainWindow.xaml.cs . Then, how can I reference this instance inside MainWindow.xaml.cs , eg to get value from MainWindowViewModel.ViewModelLocator.FirstModel.Message ?

Like this:

MainWindowViewModel viewModel = this.Resources["MainWindowViewModel"] as MainWindowViewModel;
//access any properties of viewModel here...

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