cobek Ответов: 1

Соответствующая реализация шаблона MVVM в WPF


Всем привет,
Я создал Простое Приложение Для Чата используя WPF/WCF, и у меня есть сомнения, правильно ли я реализовал в нем шаблон MVVM.
Главное окно чата имеет базовую модель представления, которая назначается свойству window DataContext (стандартное решение, я думаю):
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() { ... }
    .
    .
}

Свойство ChatUser.Nickname из модели представления привязано к заголовку главного окна чата в соответствующем файле XAML, так что имя пользователя отображается в строке заголовка окна при входе пользователя в систему.
На стороне сервера реализован чат-сервис со следующими контрактами:
[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();
}

Прежде чем пользователь сможет начать общаться в чате, он должен войти в систему. В окне входа в систему можно указать псевдоним, который будет сверен с псевдонимами вошедших в систему пользователей.

Для того чтобы использовать уже определенные методы и свойства в ChatWindowViewModel, например:
- привязать текстовое поле окна входа в систему (где пользователь вводит свой ник) к свойству ChatUser.Nickname
- проверить, не выбрал ли уже какой-либо зарегистрированный пользователь указанный ник (используя сервис Join contract),
Я назначил ChatWindowViewModel свойству DataContext окна входа в систему:
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);
}

Таким образом, я мог использовать функциональных возможностей, определенных в ChatWindowViewModel, в окне входа в систему, например такой:
// 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();
    }
}

Наконец, вернемся к вопросу: правильно ли это делать что-то подобное в MVVM?

Что я уже пробовал:

Может быть, я был немного длинноват в своем описании, извиняясь за это, я просто хотел объяснить, что я пытался сделать. Спасибо за комментарии.

1 Ответов

Рейтинг:
1

Pete O'Hanlon

Кое-что из того, что у вас есть, движется к правильному решению. Однако вы внезапно отклоняетесь от курса, когда вводите жесткую связь в код позади окон, где вы начинаете напрямую ссылаться на методы и поведение ViewModel. Часть проблемы, я думаю, заключается в том, что у вас есть нечеткое представление о том, к чему на самом деле относится модельная часть MVVM. Например, у вас есть код для добавления к активным пользователям - это идеальный кандидат для модели (модели-это не просто объекты данных, они могут быть бизнес-логикой и т. д.), Так что у вас может быть что-то вроде этого:

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);
  }
}
Затем вы хотели бы подключить это к кнопке OK в качестве команды.


cobek

Привет, Пит, спасибо за ответ. Вы правы насчет кода и некоторых других вещей, о которых вы упомянули. Этот код существует в LoginWindow (или LoginDialog), а не в ChatWindow, и я знал об этом. Я хотел бы знать, приемлема ли эта часть в ChatWindowViewModel :

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

Идея состоит в том, чтобы открыть окно входа в систему/диалог после загрузки окна клиентского чата (см. выше в ChatWindowLoaded()).

Pete O'Hanlon

Если вы спрашиваете, можно ли делиться моделями представлений между несколькими представлениями, ответ будет "да". Это не обычный сценарий, но нет ничего, что мешает вам сделать это. В большинстве случаев при открытии диалогового окна необходимо использовать слой абстракции, чтобы отделить представление от виртуальной машины, которая его создает.