Search code examples
c#wpfdata-binding

Updating user control content from a different control


I am new to wpf format, and ran into a problem at the end of my project. So let's say I have a top bar, with one textbox and a button. When the user clicks the button, the user control below this bar should update with the search results from the textbox, and it does, except it does not refresh the UI, only the data storage. For simplicity I will post a demo code modelling the issue, with a single string property.

<!-- the main window -->
 <Window.DataContext>
    <local:CustomerViewModel/>
</Window.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Grid.Row="0">
        <Label Content="content of the textbox: " Margin="10"/>
        <TextBox Width="300" Text="{Binding Customer.Name}"/>
        <Button Content="Update" Command="{Binding UpdateCommand}"/>
    </StackPanel>


    <DockPanel Grid.Row="1">
        <local:TestControl />
    </DockPanel>

user control:

 <!-- the user control named TestControl-->
<UserControl.DataContext>
    <local:CustomerViewModel />
</UserControl.DataContext>
<DockPanel LastChildFill="True">
        <Label DockPanel.Dock="Top" Content="Saved" />
        <Label DockPanel.Dock="Bottom" Content="{Binding Info, UpdateSourceTrigger=PropertyChanged}" />
    </DockPanel>

datamodel class:

 public class Customer : ObservableObject
{
    private string mName;

    public string Name
    {
        get => mName;
        set
        {
            if (value != mName)
            {
                mName = value;
                OnPropertyChanged(nameof(Name));
            }
        }
    }     

}

viewmodel class:

 public class CustomerViewModel : ObservableObject
{
    private Customer mCustomer;

    public Customer Customer
    {
        get => mCustomer;
        set
        {
            if (value != mCustomer)
            {
                mCustomer = value;
                OnPropertyChanged(nameof(Customer));
            }
        }
    }

    private string mInfo;

    public string Info
    {
        get => mInfo;
        set
        {
            if (value != mInfo)
            {
                mInfo = value;
                OnPropertyChanged(nameof(Info));
            }
        }
    }

    private ICommand mUpdateCommand;

    public ICommand UpdateCommand
    {
        get
        {
            if (mUpdateCommand == null)
            {
                mUpdateCommand = new RelayCommand(p => SaveChanges());
            }
            return mUpdateCommand;
        }

    }

    public void SaveChanges()
    {
        Info = Customer.Name + " was updated";
        MessageBox.Show(Info);
    }

    public CustomerViewModel()
    {
        mCustomer = new Customer();
        mCustomer.Name = "Test";          
        Info = mCustomer.Name;
    }


}

The correct value is displayed in the messagebox, but it does not change in the user control. I am calling the property changed interface, and have tried to invoke the button press with dispatcher.invoke, same issue, am I missing something very obvious here?


Solution

  • Your usercontrol is creating its own personal instance of the viewmodel, and using that for its DataContext. That usercontrol instance then sets Info on itself, not on the CustomerViewModel that the parent window has for its own datacontext.

    <UserControl.DataContext>
        <local:CustomerViewModel />
    </UserControl.DataContext>
    

    Remove those three lines from your usercontrol. Keep the corresponding lines in the window. The usercontrol will then inherit its datacontext from its parent, and they'll both be on the same page.

    Those three lines aren't just declaring the type of viewmodel the view uses; they're creating an actual instance of the class and assigning it to DataContext.