Search code examples
c#wpfxamlmvvmbinding

Binding attached properties and view models in MVVM | WPF


There's my project tree:

MRE_WPF
|
--ViewModels
  |
  --MainWindowButtons
    |
    --HomeButtonViewModel.cs
    --UserSettingsButtonViewModel.cs
--MainWindow.xaml
--MainWindow.xaml.cs

I have three buttons on main window.

<Window x:Class="MRE_WPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MRE_WPF"
        xmlns:main_window_buttons="clr-namespace:MRE_WPF.ViewModels.MainWindowButtons"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Canvas Height="480" Name="MainCanvas">
        <Button x:Name="homeButton" 
                Height="55" 
                Width="75" 
                Background="#FF334166" 
                Foreground="{x:Null}" 
                FontSize="24" 
                BorderBrush="{x:Null}" 
                FontFamily="Tahoma"
                Canvas.Top="{Binding Path=(main_window_buttons:HomeButtonViewModel.CanvasTop), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
                >
            <TextBlock Text="Home" Foreground="White"/>
        </Button>

        <Button x:Name="userSetingsButton" 
                Height="55"
                Width="75"
                Background="#FF334166"
                Foreground="{x:Null}"
                FontSize="20"
                BorderBrush="{x:Null}"
                FontFamily="Tahoma"
                Canvas.Top="{Binding Path=(main_window_buttons:UserSettingsButtonViewModel.CanvasTop), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
                Canvas.Left="{Binding Path=(main_window_buttons:UserSettingsButtonViewModel.CanvasLeft), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
                >
            <TextBlock Text="User set" Foreground="White"/>
        </Button>

        <Button x:Name="changeButton" 
                Height="55" 
                Width="75" 
                Background="#FF334166" 
                Foreground="{x:Null}" 
                FontSize="20" 
                Click="changeButton_Click" 
                BorderBrush="{x:Null}" 
                FontFamily="Tahoma" 
                Canvas.Top="100"
                Canvas.Left="500">
            <TextBlock Text="Change" Foreground="White"></TextBlock>
        </Button>
    </Canvas>
</Window>

HomeButtonViewModel class:

public class HomeButtonViewModel:INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    private double canvasTop;

    public double CanvasTop {
        get => canvasTop;
        set {
            canvasTop = value;
            OnPropertyChanged(nameof(CanvasTop));
        }
    }

    public void OnPropertyChanged([CallerMemberName] string name = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

UserSettingsButtonViewModel is pretty much the same:

public class UserSettingsButtonViewModel:INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    private double canvasTop = 102;
    private double canvasLeft = -1;

    public double CanvasTop {
        get => canvasTop;
        set {
            canvasTop = value;
            OnPropertyChanged(nameof(CanvasTop));
        }
    }

    public double CanvasLeft {
        get => canvasLeft;
        set {
            canvasLeft = value;
            OnPropertyChanged(nameof(canvasLeft));
        }
    }

    public void OnPropertyChanged([CallerMemberName] string name = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

I want to change those properties on click on button Change:

private void changeButton_Click(object sender, RoutedEventArgs e) {
    bool mode = !(bool)Settings.Default["mode7"];
    Settings.Default["mode7"] = mode;
    Settings.Default.Save();
    var h = new HomeButtonViewModel() {
        CanvasTop = mode ? 1 : 0
    };
    var us = new UserSettingsButtonViewModel() {
        CanvasTop = mode ? 102 : double.NaN,
        CanvasLeft = mode ? -1 : double.NaN
    };
}

But on click nothing happens. I know that I do something wrong, but I don't know what.

//Also I want use command instead of Click, but let's solve one problem per question.


Solution

  • It's unclear why you have two view models.

    Anyway, you should set the DataContext of the window to an instance of a single view model. In this once you could then initialize the child view models:

    public class MainWindowVieModel
    {
        public HomeButtonViewModel HomeButtonViewModel { get; } 
            = new HomeButtonViewModel();
    
        public UserSettingsButtonViewModel UserSettingsButtonViewModel { get; } 
            = new UserSettingsButtonViewModel();
    }
    

    You could then bind to the child view models through the main view model/DataContext in the view using the following syntax:

    <Button x:Name="homeButton" 
                    Height="55" 
                    Width="75" 
                    Background="#FF334166" 
                    Foreground="{x:Null}" 
                    FontSize="24" 
                    BorderBrush="{x:Null}" 
                    FontFamily="Tahoma"
                    Canvas.Top="{Binding HomeButtonViewModel.CanvasTop}"
                    >
        <TextBlock Text="Home" Foreground="White"/>
    </Button>
    
    <Button x:Name="userSetingsButton" 
                    Height="55"
                    Width="75"
                    Background="#FF334166"
                    Foreground="{x:Null}"
                    FontSize="20"
                    BorderBrush="{x:Null}"
                    FontFamily="Tahoma"
                    Canvas.Top="{Binding UserSettingsButtonViewModel.CanvasTop}"
                    Canvas.Left="{Binding UserSettingsButtonViewModel.CanvasLeft}"
                    >
        <TextBlock Text="User set" Foreground="White"/>
    </Button>
    

    Finally, you need to set the properties of the view model instance(s) that you actually bind to in the code-behind of the window (where you should also set the DataContext of the window):

    public partial class MainWindow : Window
    {
        private readonly MainWindowVieModel _viewModel = new MainWindowVieModel();
    
        public MainWindow()
        {
            InitializeComponent();
            DataContext = _viewModel;
        }
    
        private void changeButton_Click(object sender, RoutedEventArgs e)
        {
            bool mode = !(bool)Settings.Default["mode7"];
            Settings.Default["mode7"] = mode;
            Settings.Default.Save();
    
            _viewModel.HomeButtonViewModel.CanvasTop = mode ? 1 : 0;
    
            _viewModel.UserSettingsButtonViewModel.CanvasTop = mode ? 102 : double.NaN;
            _viewModel.UserSettingsButtonViewModel.CanvasLeft = mode ? -1 : double.NaN;
        }
    }