Search code examples
c#wpfmvvmviewcommand-line-interface

WPF MVVM Button from View control interface


I'm pretty new to WPF and MVVM concept and i'm trying to solve probably stupid issue. I have MainViewModel where I control what should be seen on the screen according to pressed buttons in the menu.

  class MainViewModel : ObservableObject
{
    public RelayCommand HomeViewCommand { get; set; }       
    public RelayCommand SettingsViewCommand { get; set; }
    public HomeViewModel HomeVM { get; set; }      
    public SettingsViewModel SettingsVM { get; set; }
    private object _currentView;
    public object CurrentView
    {
        get { return _currentView; }
        set 
        { 
            _currentView = value;
            OnPropertyChanged();
        }
    }

    public MainViewModel()
    {
        HomeVM = new HomeViewModel();            
        SettingsVM = new SettingsViewModel();
        CurrentView = HomeVM;
        HomeViewCommand = new RelayCommand(o =>
        {
            CurrentView = HomeVM;
        });          

        SettingsViewCommand = new RelayCommand(o =>
        {
            CurrentView = SettingsVM;
        });
    }

One of my Views is SettingsView. In this View I have a button, which should check if the connection string is allright. And because I am going to use kinda lot SQL commands my thought was, to put all code regarding SQL into one folder. So basically the project is MVVM(file)>View(file),ViewModel(file).... and SQL(file)>.... Sadly when I add to the SettingsView sql, the app falls because of 'Object reference not set to an instance of an object'

In the SettingsView is:

 public partial class SettingsView : UserControl
{
    private readonly HSQLTestConnection _SQLTestConnection;         
    public SettingsView(HSQLTestConnection SQLTestConnection)
    {
        InitializeComponent();
        _SQLTestConnection = SQLTestConnection;

And button is like this: _SQLTestConnection.TryConnectionString(ConnectionString);

Model is empty:

class SettingsViewModel
{
    public SettingsViewModel()
    {
    }
}

Interface for SQL is:

 public interface HSQLTestConnection
{       
    void TryConnectionString(string ConnectionString);
}

And SQL function:

public class SQLTestConnection : HSQLTestConnection
{       
    public void TryConnectionString(string ConnectionString)
    {           
       //do something
    }
}

App does not show any error and was working and changing Views pretty fine, I think the issue is with public SettingsView(HSQLTestConnection SQLTestConnection) But I could not find how to solve this. Since I am going to have multiple classes for SQL, I wanted to solve it this way. It used to work in different app I wrote, but I was not using RellayCommand, which I wanted to try. Thank you for your help in advance.


Solution

  • I see that SettingsView class extends the UserControl class. This is problematic because

    • you say you want to follow the MVVM design pattern but your user control code (SettingsView) has a reference to your database connection (HSQLTestConnection) in the constructor. You should not mix your view code with your database code.
    • your SettingsView is initialized by the framework so where is it supposed to get the value for the HSQLTestConnection parameter in the constructor?

    In any case, your view should not have any database logic in it, at least not if you want to follow MVVM.

    What you could do is to initiate your database connection in the view model of the settings view SettingsViewModel. Not great, but somewhat better.

    Since you mentioned there will be "kinda lot SQL commands", I would suggest you separate this code into a dedicated service, and then use this service in your view models where applicable.

    public interface IMyDatabaseService
    {
        bool TestConnection();
        IEnumerable<SomeDataObject> GetData();
        ...
    }
    

    You would then pass this interface where needed:

    public class SettingsViewModel : INotifyPropertyChanged
    {
        private readonly IMyDatabaseService databaseService;
        private bool connectionSuccessful;
    
        public RelayCommand TestConnectionCommand { get; set; }
        public bool ConnectionSuccessful 
        { 
            get => connectionSuccessful; 
            set 
            { 
                connectionSuccessfull = value;
                var propertyName = nameof(ConnectionSuccessful);
                PropertyChanged?.Invoke(this, new (propertyName));
            }
        }
    
        public SettingsViewModel(IMyDatabaseService databaseService)
        {
            this.databaseService = databaseService;
            TestConnectionCommand = new RelayCommand(_ => 
                    ConnectionSuccessful = databaseService.TestConnection()
            );
            ...
        }
    }
    
    public class MainViewModel
    {
        ...
    
        public MainViewModel()
        {
            ...
            IMyDatabaseService databaseService = new MyDatabaseService();
            SettingsVM = new SettingsViewModel(databaseService);
            ...
        }
        ...
    }
    

    Notice the TestConnectionCommand as well as the ConnectionSuccesful boolean.

    The command will call your database service and this is what you will bind your button to.

    Similarly, the boolean can be bound to in your view to show the connection state. Since this value can change whenever you run the command, you have to invoke PropertyChanged to let the framework know the value has changed. In this case, the invocation is done in the setter.

    Assuming your data context is set correctly, in your SettingsView view you would now have:

        ...
        <Button Command="{Binding TestConnectionCommand}"> Test </Button>
        
        <TextBlock Text="Connection failed!">
            <TextBlock.Style>
                <Style TargetType="TextBlock">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ConnectionSuccessfull}" Value="True">
                            <Setter Property="Text" Value="Connection successful!" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
        </TextBlock>
        ...
    

    I used a text block with a data trigger to show the connection state, but you can use the boolean and the data trigger to do whatever you wish. Maybe show a modal dialog or something.

    Better still would be to use some sort of dependency injection to inject your database service and other dependencies as well as making the database code async so that your UI won't freeze, but that will be for the future you to figure out.