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
{
public ChatService.ChatClient client;
InstanceContext instanceContext;
public bool askToExitApp;
private User chatUser;
public User ChatUser
{
get { return chatUser; }
set
{
chatUser = value;
OnPropertyChanged("ChatUser");
}
}
public ObservableCollection<User> AllActiveUsers { get; set; }
public ObservableCollection<ChatMessage> AllMessages { get; set; }
public ChatMessage CurrentMessage { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void UserSentMessage(ChatMessageDTO msgDTO){ ... }
public void UserJoined(ChatMessageDTO msgDTO) { ... }
public void UserLeft(ChatMessageDTO msgDTO) { ... }
public void IsAlive() { ... }
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 };
}
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;
ret = loginDlg.ShowDialog();
}
if (ret == false)
MainWindowRequestCloseCommand.Execute(null);
}
This way I could use functionalites defined in ChatWindowViewModel, within the login window, for example like this:
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.