Click here to Skip to main content
15,890,512 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi all,
I created a Simple Chat Application using WPF/WCF and I have doubts if I correctly implemented MVVM pattern in it.
The main chat window has underlying view model which is assigned to window DataContext property (standard solution, I guess):
class ChatWindowViewModel : INotifyPropertyChanged, IChatCallback
{
    //fields and properties
    public ChatService.ChatClient client;
    InstanceContext instanceContext;
    public bool askToExitApp;

    private User chatUser;
    public User ChatUser
    {
        get { return chatUser; }
        set
        {
            chatUser = value;
            OnPropertyChanged("ChatUser");
        }
    }

    // storage for all logged users, they will be shown in datagrid
    public ObservableCollection<User> AllActiveUsers { get; set; }

    // storage for all sent messages, they will be shown in datagrid
    public ObservableCollection<ChatMessage> AllMessages { get; set; }

    // storage for a message, being typed by user in chat
    public ChatMessage CurrentMessage { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    //implementation of the IChatCallback callback interface
    public void UserSentMessage(ChatMessageDTO msgDTO){ ... }
    public void UserJoined(ChatMessageDTO msgDTO) { ... }
    public void UserLeft(ChatMessageDTO msgDTO) { ... }
    public void IsAlive() { ... }

    //constructor
    public ChatWindowViewModel()
    {
        instanceContext = new InstanceContext(this);
        askToExitApp = true;
        AllActiveUsers = new ObservableCollection<User>();
        AllMessages = new ObservableCollection<ChatMessage>();
        CurrentMessage = new ChatMessage();
        ChatUser = new User { Nickname = string.Empty };
    }

    //commands and everything related to them
    private ICommand chatWindowLoadedCommand;
    public ICommand ChatWindowLoadedCommand { ... }
    private void ChatWindowLoaded() { ... }
    .
    .
}

The ChatUser.Nickname property from the view model is bound to the main chat window title in corresponding XAML file, so that user name is shown in the window title bar when a user logs in.
On the server side, a chat service is implemented with following contracts:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]
interface IChat
{
    [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
    void SendMessage(ChatMessageDTO msg);

    [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
    string Join(UserDTO user, out UserDTO[] activeUsers);

    [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
    void Leave();
}

Before a user can start with chatting, he/she/it has to log in. In the login window a nickname can be specified and it will be checked against nicknames of the logged users.

In order to use already defined methods and properties in ChatWindowViewModel, for example:
- to bind login window textbox (where user types his nickname) with ChatUser.Nickname property
- to check if any logged user has already chosen the specified nickname (using service Join contract),
I assigned ChatWindowViewModel to the DataContext property of the login window:
private void ChatWindowLoaded()
{
    client = new ChatService.ChatClientinstanceContext);
    client.Open();

    bool? ret = true;
    using (LoginDialog loginDlg = new LoginDialog())
    {
        loginDlg.DataContext = this; //!!!! is this ok?
        ret = loginDlg.ShowDialog();
    }

    // in case user do not want to login,
    // close chat window client application
    if (ret == false)
        MainWindowRequestCloseCommand.Execute(null);
}

This way I could use functionalites defined in ChatWindowViewModel, within the login window, for example like this:
// memeber of LoginWindow class
     private void DummyHandler(object sender, EventArgs e)
{
    try
    {
        UserDTO[] activeUsers;
        string result = ((ChatWindowViewModel)DataContext).client.Join(((ChatWindowViewModel)DataContext).ChatUser.ToUserDTO(), out activeUsers);
        if (result != "OK")
        {
            MessageBox.Show(result);
            ((ChatWindowViewModel)DataContext).askToExitApp = false;
            DialogResult = false;
        }
        else
        {
            ((ChatWindowViewModel)DataContext).CurrentMessage.Sender = ((ChatWindowViewModel)DataContext).ChatUser;
            foreach (var user in activeUsers.OrderBy(x => x.Nickname).Select(x => x.ToUser()))
                ((ChatWindowViewModel)DataContext).AllActiveUsers.Add(user);
            DialogResult = true;
            Close();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("Unexpected error occured. Application will be terminated.");
        ((ChatWindowViewModel)DataContext).askToExitApp = false;
        DialogResult = false;
        Close();
    }
}

Finally, to get back to the question: Is this a correct way to do something like this in MVVM?

What I have tried:

I was maybe a little bit lengthy in my description, an apology for that, I just wanted to explain what I tried to do. Thanks for the comments.
Posted
Updated 31-May-17 1:44am
v3

1 solution

Some of what you have is moving towards the correct solution. However you veer suddenly off track when you introduce a hard-coupling in the code behind of the windows where you start to refer directly to methods and behaviours of the ViewModel. Part of the problem I think is that you have a fuzzy idea of what the model part of MVVM actually refers to. For instance, you have code to add to the active users - this is a perfect candidate for a model (models aren't just data entities, they can be business logic, etc), so you could have something like this:
C#
private IUserDetails _userDetails;

public ChatWindowViewModel(IUserDetails userDetails)
{
  _userDetails = userDetails;
}

private void AddUser()
{
  CurrentMessage.Sender = ChatUser;
  foreach (var activeUser in activeUsers)
  {
    var user = _userDetails.GetFromNickname(activeUser.Nickname);
    if (user == null) continue;
    AllActiveUsers.Add(user);
  }
}
Then, you would look to hook this up to the OK button as a Command.
 
Share this answer
 
Comments
cobek 31-May-17 16:36pm    
Hi Pete thanks for the answer. You are right about the code behind and some other things you mentioned. This code behind exists in LoginWindow (or LoginDialog) not in ChatWindow and I was aware of it. I wanted to know if this part is acceptable in ChatWindowViewModel :

            using (LoginDialog loginDlg = new LoginDialog())
            {
                loginDlg.DataContext = this; //!!!! is this ok?
                ret = loginDlg.ShowDialog();
            }

The idea is to open a login window/dialog, after client chat window has been loaded (see above in ChatWindowLoaded()).
Pete O'Hanlon 1-Jun-17 10:21am    
If you're asking if it's okay to share view models between multiple views, the answer is yes it is. It's not a common scenario but there's nothing that precludes you from doing that. In most cases, when opening a dialog, you would want to use an abstraction layer to decouple the view from the VM that is creating it.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900