5

I'm a true beginner in MVVM pattern. I'm trying to change the backgound of a grid on button's click. I have a xaml with a grid containing a button, and a ViewModel .cs from where i want to change the grid's background on button's click. Until there i just succeed to show up a MessageBox when i clicked...

.xaml code:

<Window x:Class="WpfSimple.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfSimple"
    Title="MainWindow" Height="150" Width="370">
<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>
    <Grid>
    <Button Content="Click" 
            Height="23" 
            HorizontalAlignment="Left"
            Background="Gray"
            Margin="75.944,47.465,0,0" 
            Name="btnClick" 
            VerticalAlignment="Top" 
            Width="203"
            Command="{Binding ButtonCommand}"/>
        <!--What is necessary to add for changing grid color ? Commandparameter ?-->
</Grid>

MainWindowViewModel.cs code:

namespace WpfSimple
{
    class MainWindowViewModel
    {
        private ICommand m_ButtonCommand;
        public ICommand ButtonCommand
        {
            get
            {
                return m_ButtonCommand;
            }
            set
            {
                m_ButtonCommand = value;
            }
        }

        public MainWindowViewModel()
        {
            ButtonCommand=new RelayCommand(new Action<object>(ChangeBgColor));
        }

        public void ChangeBgColor(object obj)
        {
            /*HERE I WANT TO CHANGE GRID COLOR*/
        }
    }
}

Sorry for my bad english.

Best regards.

Termininja
  • 6,620
  • 12
  • 48
  • 49
Chefty
  • 149
  • 1
  • 13

2 Answers2

2

Fitst of all you should implement INotifyPropertyChanged in your ViewModel:

public class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    // This method is called by the Set accessor of each property.
    // The CallerMemberName attribute that is applied to the optional propertyName
    // parameter causes the property name of the caller to be substituted as an argument.
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Then, add NotifyPropertyChanged() to your properties setter.

Ok. Next, add new Property with your grid background color to your ViewModel:

private Brush _gridBackground;
public Brush GridBackground
{ 
    get { return _gridBackground; }
    set
    {
        _gridBackground = value;
        NotifyPropertyChanged();
    }
}

And bind your grid's background to your property:

<Grid Background="{Binding GridBackground}">

Finally you can just change GridBackground in the command handler:

public void ChangeBgColor(object obj)
{
    GridBackground = Brushes.Blue;
}

You should remember that it's a bad practice to add WPF-classes like Brush to your code. The better way is to use IValueConverter in XAML code and BCL classes in your ViewModel. For example, you can use enumeration in ViewModel and convert it to brush in ValueConverter.

  1. Add new enum for your ViewModel's property:

    public enum GridState { Valid, Invalid }
    
  2. Change property type:

    private GridState _gridBackground;
    public GridState GridBackground
    { 
        get { return _gridBackground; }
        set
        {
            _gridBackground = value;
            NotifyPropertyChanged();
        }
    }
    
  3. Add new class with value converter

    public class GridStateToBackgroundColorConverter : IValueConverter
    {
        #region IValueConverter Members
    
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            GridState val = (GridState) value;
            if(val == GridState.Valid)
                return Brushes.Green;
            return Brushes.Red;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    
        #endregion
    }
    
  4. Add new static resource to your control

     <UserControl.Resources>
         <converters:GridStateToBackgroundColorConverter x:Key="gridStateToBackgroundColorConverter" />
     </UserControl.Resources>
    
  5. Update binding to your property

    <Grid Background="{Binding GridBackground, Converter={StaticResource gridStateToBackgroundColorConverter}">
    
Vadim Martynov
  • 8,602
  • 5
  • 31
  • 43
  • YES ! Thank you very much Vadim Martynov ! I'm not sure that i understood the purpose of "INotifyPropertyChanged Interface" because it's notified me nothing at all... but your answer was really helpful. Best regards. – Chefty Dec 17 '15 at 10:40
  • @Chefty INPC is required for data bindng. Without it your color will not change when you'll change property in ViewModel. Also, INPC is a generally feature of data binding. You can read more on MSDN: https://msdn.microsoft.com/en-US/library/ms752347(v=vs.100).aspx – Vadim Martynov Dec 17 '15 at 10:48
  • I cant add new static resource to my control, it's make an error : "GridStateToBackgroundColorConverter is not supported in a WPF project" is it normal ? – Chefty Dec 17 '15 at 11:11
  • Totally not. And it's hard to understand the problem. 1. Does your converter actally implements IValueConverter from System.Windows.Data? 2. Is your converter class in the same project with view? I find an instruction here: http://stackoverflow.com/questions/28607372/why-would-i-get-a-booltorowheightconverter-is-not-supported-in-a-windows-presen I think that you should to add converters namespace like in that answer: xmlns:converters="clr-namespace:YourAppName.YourNamespaceWithConverter" – Vadim Martynov Dec 17 '15 at 11:18
  • Yes IValueConverter is implements from System.Windows.Data, now i can't make the converters namespace works. My app name is "WpfSimple" and the namespace containing the Converter class is also named "WpfSimple"... I copied your class "GridStateToBackgroundColorConverter" inside the first class "MainWindowViewModel" because it was where i declared "GridState" variable. Was it a bad idea ? – Chefty Dec 17 '15 at 11:54
  • Inside means nested? Typically WPF application contains many Views, ViewModels, Converters and Models (DTOs or smart models). Then almost every WPF project contains this 4 folders. Inside each folder you have 1 file per 1 class with same namespace. For example Converters\GridStateToBackgroundColorConverter.cs contains this code: namespace WpfSimple.Converters { public class GridStateToBackgroundColorConverter : IValueConverter {...} } Then for 1 view to add many converters you can add only 1 namespace xmlns:converters="clr-namespace:WpfSimple.Converters". – Vadim Martynov Dec 17 '15 at 12:27
  • @Chefty as you can see in my previous comment and (articles about project structure)[http://stackoverflow.com/questions/18825888/project-structure-for-mvvm-in-wpf] the better way is to separate each class in their files and store all your converters in one Folder/Namespace. This is not only about your question but I hope that this information will help you in future. – Vadim Martynov Dec 17 '15 at 12:32
  • Yeah i understand the idea, thank you. My first purpose was to change the background picture when mouse is over the button. I will finally make xaml pages, and edit animations/pages changes in xaml.cs. Anyway, thank you for all Vadim Martynov ! – Chefty Dec 17 '15 at 15:26
-1

If you want to change the grid background color then you can use command parameter. You can pass any UI control as Command parameter. In your case pass grid to access grid in your view model. Give the name to your grid and use that name to use as command parameter. For this you need to implement the code like this:

<Window x:Class="WpfSimple.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfSimple"
        Title="MainWindow" Height="150" Width="370">
<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>
<Grid Name="grid">
<Button Content="Click" 
        Height="23" 
        HorizontalAlignment="Left"
        Background="Gray"
        Margin="75.944,47.465,0,0" 
        Name="btnClick" 
        VerticalAlignment="Top" 
        Width="203"
        Command="{Binding ButtonCommand}"
        CommandParameter="{Binding Elementname="grid"}"/>
</Grid>

After making this change to the .xaml file. Implement Parameterized Relay Command to use this passed Grid to use in your Viewmodel file. To Implement Parameterized Relay Command Try to Implement following code:

    private ICommand m_ButtonCommand;
    public ICommand ButtonCommand
    {
        get
        {
            return m_ButtonCommand;
        }
        set
        {
            m_ButtonCommand = value;
        }
    }

    public MainWindowViewModel()
    {
        ButtonCommand=new RelayCommand<Grid>(ChangeBgColor);
    }

    public void ChangeBgColor(Grid grid)
    {
        if(grid!=null)
            grid.Background = Brushes.Red; //Any color you want to change.
    }

I hope this will work. Thank you.