Marcel Braasch
Marcel Braasch

Reputation: 1183

Properties are null when calling a command method

When RefreshServices() is called, both SelectedComputerand SelectedCustomerare null, even though they are not when I actually select them.

Why?

/edit: Forgot to post the buttons' declarations in the XAML. It's updated now.

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    MainViewModel _main = new MainViewModel();
    public MainWindow()
    {
        InitializeComponent();
        DataContext = _main;
    }
}

XAML:

<Window.Resources>
    <vm:MainViewModel x:Key="viewModel" />
</Window.Resources>
<Border Margin="10">
    <Grid>


        <!-- Comboboxes -->
        <GroupBox Header="Computer" Grid.Column="0" Grid.ColumnSpan="4" Margin="0 0 4 4">
            <ComboBox ItemsSource="{Binding ComputerNames}"
                      SelectedItem="{Binding SelectedComputer}"
                      IsSynchronizedWithCurrentItem="True"
                      SelectedIndex="0"/>
        </GroupBox>
        <GroupBox Header="Customer" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Margin="0 0 4 4" >
            <ComboBox ItemsSource="{Binding CustomerNames}"
                      SelectedItem="{Binding SelectedCustomer}"
                      IsSynchronizedWithCurrentItem="True"
                      SelectedIndex="0"/>
        </GroupBox>

        <!-- Main list -->
        <DataGrid x:Name="dataGrid" Grid.Row="2" Grid.ColumnSpan="8"
                  ItemsSource="{Binding Path=Services, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
                  AutoGenerateColumns="False"
                  IsReadOnly="True">

            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Width="*"  Binding="{Binding DisplayName}"/>
                <DataGridTextColumn Header="Status" Width="*" Binding="{Binding Status}" />
                <DataGridTextColumn Header="Machine Name" Width="*" Binding="{Binding MachineName}" />
            </DataGrid.Columns>
        </DataGrid>

        <!-- Buttons-->
        <Button Grid.Row="5" Grid.Column="0" Margin="0 4 4 4" Content="Start"
                Command="{Binding Path=StartServiceCommand, Source={StaticResource viewModel}}"
                CommandParameter="{Binding SelectedItems, ElementName=dataGrid}"/>
        <Button Grid.Row="5" Grid.Column="1" Margin="4 4 0 4" Content="Stop"
                Command="{Binding Path=StopServiceCommand, Source={StaticResource viewModel}}"
                CommandParameter="{Binding SelectedItems, ElementName=dataGrid}"/>
        <Button Grid.Row="5" Grid.Column="3" Margin="4 4 0 4" Content="Refresh"
                Command="{Binding Path=RefreshServicesCommand, Source={StaticResource viewModel}}" />


    </Grid>
</Border>

MainViewModel.cs public class MainViewModel : INotifyPropertyChanged { #region INotify methods public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
    #endregion

    /*---------------------------- C U S T O M E R S -----------------------------*/

    #region Customer Properties

    private string _selectedCustomer;
    private ObservableCollection<string> _customerNames;

    public string SelectedCustomer
    {
        get => _selectedCustomer;
        set
        {
            SetField(ref _selectedCustomer, value);
            Services = Utils.UpdatedServices(SelectedComputer, SelectedCustomer);
        }
    }

    public ObservableCollection<string> CustomerNames
    {
        get => _customerNames;
        set
        {
            SetField(ref _customerNames, value);
            Services = Utils.UpdatedServices(SelectedComputer, SelectedCustomer);
        }
    }

    #endregion

    /*---------------------------- S E R V I C E S -----------------------------*/

    #region Services Properties
    private ObservableCollection<ServiceController> _services;
    private ObservableCollection<ServiceController> _selectedServices;

    public ObservableCollection<ServiceController> SelectedServices
    {
        get => _selectedServices;
        set => SetField(ref _selectedServices, value);
    }
    public ObservableCollection<ServiceController> Services
    {
        get => _services;
        set => SetField(ref _services, value);
    }

    #endregion

    /*---------------------------- C O M P U T E R S -----------------------------*/

    #region Computer Properties

    private string _selectedComputer;
    private ObservableCollection<string> _computerNames;


    public string SelectedComputer
    {
        get => _selectedComputer;
        set
        {
            SetField(ref _selectedComputer, value);
            CustomerNames = Utils.UpdatedCustomerNames(SelectedComputer);
        }
    }

    public ObservableCollection<string> ComputerNames
    {
        get => _computerNames;
        set => SetField(ref _computerNames, value);
    }

    #endregion

/*---------------------------- C O M M A N D S -----------------------------*/

    #region Commands

    public StartServiceCommand StartServiceCommand { get; set; }
    public void StartService(ObservableCollection<ServiceController> serviceControllers)
    {
        try
        {
            foreach(var service in serviceControllers)
                if (service.Status != ServiceControllerStatus.Running)
                    service.Start();
            RefreshServices();
        }
        catch (Exception ex) { }
    }

    public StopServiceCommand StopServiceCommand { get; set; }
    public void StopService(ObservableCollection<ServiceController> serviceControllers)
    {
        try
        {
            foreach (var service in serviceControllers)
                if (service.Status != ServiceControllerStatus.Stopped &&
                    service.Status != ServiceControllerStatus.StopPending)
                    service.Stop();
            RefreshServices();
        }
        catch (Exception ex) { }
    }

    public RefreshServicesCommand RefreshServicesCommand { get; set; }
    public void RefreshServices()
    {
        Services = Utils.UpdatedServices(SelectedComputer, SelectedCustomer);
    }

    #endregion

    public MainViewModel()
    {
        #region Initialize
        _services = new ObservableCollection<ServiceController>();
        _computerNames = new ObservableCollection<string>();
        _customerNames = new ObservableCollection<string>();
        _selectedComputer = _customerNames.FirstOrDefault();
        _selectedServices = new ObservableCollection<ServiceController>();
        ComputerNames = new ObservableCollection<string> { Environment.MachineName, Environment.MachineName };
        StartServiceCommand = new StartServiceCommand(this);
        StopServiceCommand = new StopServiceCommand(this);
        RefreshServicesCommand = new RefreshServicesCommand(this);
        #endregion
    }
}

Upvotes: 1

Views: 180

Answers (1)

You created two instances of MainViewModel. Whenever you see "everything in my viewmodel is null even though, at the same time, it isn't" on Stack Overflow, look for duplicate viewmodel instantiations in the XAML and the constructor.

You're getting the Commands, inexplicably, from a throwaway copy in Window.Resources that's used for nothing else. Don't do that. Delete Source={StaticResource viewModel} from those bindings, and I think they should work as they are.

Grid is a direct child of the Window and doesn't have its own DataContext, so this should be fine. Let me know if not. We can make it work without too much trouble.

<Button Grid.Row="5" Grid.Column="0" Margin="0 4 4 4" Content="Start"
        Command="{Binding StartServiceCommand}"
        CommandParameter="{Binding SelectedItems, ElementName=dataGrid}"/>
<Button Grid.Row="5" Grid.Column="1" Margin="4 4 0 4" Content="Stop"
        Command="{Binding StopServiceCommand}"
        CommandParameter="{Binding SelectedItems, ElementName=dataGrid}"/>
<Button Grid.Row="5" Grid.Column="3" Margin="4 4 0 4" Content="Refresh"
        Command="{Binding RefreshServicesCommand}" />

Upvotes: 4

Related Questions