Страницы

Поиск по вопросам

Показаны сообщения с ярлыком mvvm. Показать все сообщения
Показаны сообщения с ярлыком mvvm. Показать все сообщения

понедельник, 13 апреля 2020 г.

Обязательно ли все свойства биндить?

#wpf #mvvm

                    
Интересует: обязательно ли все свойства биндить (например, видимость, label content,
IsReadOnly)  или же можно передавать в ViewModel объект Вида через конструктор,  а
также какие есть еще обязательные правила или рекомендации при использовании MVVM.
Большое спасибо за помощь.    


Ответы

Ответ 1



Такой вопрос у меня обычно возникал, когда я не мог связать логику взаимодействия различных компонентов приложения. У меня было сильное искушение просто добавить объект в ViewModel. Моя рекомендация - детально разработать на листе бумаги иерархию компонентов, продумать хорошо XAML, не лениться писать кажущийся длинный код ViewModel, так как потом он себя окупит меньшими временными затратами на доработку и понимание, так как все будет структурировано. Плюс к этому вы получите хороший опыт разработки распределенных приложений (если оно у вас таковое), навыки создания более "чистого" кода. Насчет биндинга: Биндить нужно только то, что требует зависимости от данных или логики кода. В большинстве случаев мелкие нюансы убираются лучшим знанием XAML (у него есть набор Trigger, State, Storyboard, Command, element-property->property-element etc). Также если хочется что-то кастомизировать "в комплекте" - есть динамическая загрузка стилей --- Создал макет, прикрепил его к ресурсам (через Key), потом если что загрузил новый словарь ресурсов и все значения стилей, кистей и т.п. См. шаблон Visual Studio - CustomControl. Насчет связи компонентов: в WPF (как и в C#) есть очень удобная вещь - Prism. Всю ее можно и не знать сразу, но вот к примеру EventAggregator очень даже полезен при связи компонентов. Если у вас разные компоненты, которые не знают друг о друге ничего, то он вам очень поможет. Также для общего представления о связывании я почитал бы на MSDN про MEF.

воскресенье, 15 марта 2020 г.

Связь между ViewModel в паттерне MVVM

#c_sharp #net #wpf #mvvm


Здравствуйте. Хотелось бы вашего совета. Только начинаю использовать MVVM. Допустим,
есть окно MainWindow, в котором находится статус-бар и некоторая индикацию. В это окно
помещается, к примеру, UserControl, где находится некоторое меню.
По логике вещей, у окна и usercontrol'a свои ViewModel. Вопрос: как тогда обратиться
из одной viewmodel в другую? Например, после выполнения некоторого действия из меню,
мне нужно изменить текст в статус-баре.
Конечно передавать объект одной viewmodel'и в другой нельзя. Нарушение инкапсуляции. 
    


Ответы

Ответ 1



В принципе, ничего страшного нету в том, чтобы одна VM знала о другой. Например, корневая VM обычно имеет ссылки на вложенные VM, и образует таким образом дерево. С другой стороны, для сценария, который вы описываете, сообщение проходит из меню к статус-бару, должно работать не так. Для развязки точки, где генерируется событие, и точки где происходит реакция на него, используются команды. Я бы воспользовался такой архитектурой: Пункт меню привязан к команде (RoutedUICommand). Например, одной из ApplicationCommands, или вы можете определить свою команду тоже¹. Вам нужно будет зарегистрировать обработчики для команд при помощи CommandBinding, глобально через CommandManager.RegisterClassCommandBinding или локально через XAML (). Таким образом, VM, которая зарегистрировала обработчик команды, получит управление при выборе этой команды в меню. Если результат выполнения устанавливает статус, VM, которая выполняет команду, должна знать кому сообщить об изменении статуса. Поскольку статус один на приложение, вы можете сделать его глобально доступным (VM-синглтон или публичное свойство корневой VM), а можете и развязать эту зависимость при помощи ещё одной команды. Заметьте, что есть более прямой путь работы с командами, когда вы просто определяете класс, имплементирующий интерфейс ICommand (CanExecute + Execute + CanExecuteChanged). Это скорее всего не то, что вам надо, потому что такую команду сложнее «отвязать»: вы должны будете как-то передать экземпляр команды в меню. Для случая RoutedUICommand меню знает только какую команду оно хочет выполнить, а подсоединение исполнителя к команде происходит отдельно и независимо. ¹ Например, так: static class ApplicationExtendedCommands { public static ApplicationExtendedCommands Kaboom = new RoutedUICommand() { Text = "Destroy the planet" }; public static ApplicationExtendedCommands About = new RoutedUICommand() { Text = "About application" }; }

пятница, 13 марта 2020 г.

Привязка к StackPanel

#c_sharp #wpf #mvvm


Как к StackPanel привязать ObservalCollection? Вот так я понимаю нельзя: 



    


Ответы

Ответ 1



Прямо к StackPanel привязаться нельзя, у неё нет понятия «источника внутренних элементов». Но для таких целей есть ItemsControl (или производные от него):

понедельник, 9 марта 2020 г.

Моргающее в панели задач окно

#c_sharp #wpf #xaml #mvvm


Подскажите пожалуйста как заставить окно моргать в панели задач? Ну так часто делают
типа какое то событие в окне произошло, а оно свернуто и вот такое извещение.

@VladD хз я может что то не так делаю, но у меня никак не хочет это работать. Я ставил
точку останова у меня при попытке создать экземпляр WindowInteropHelper выходит из
функции вообще. Тестирую вот так:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        var t = new Timer(5000);
        t.Elapsed += Elapsed;
        t.Start();
    }

    [DllImport("user32.dll")]
    public static extern int FlashWindow(IntPtr Hwnd, bool Revert);

    private void Elapsed(object sender, ElapsedEventArgs e)
    {
        var hwnd = new WindowInteropHelper(this).Handle;
        FlashWindow(hwnd, true);
    }
}

    


Ответы

Ответ 1



Попробуйте так: [DllImport("user32.dll")] public static extern int FlashWindow(IntPtr Hwnd, bool Revert); // ... var hwnd = new WindowInteropHelper(window).Handle; FlashWindow(hwnd, true); Вызывать эту функцию на таймере не стоит, т. к. он доставляет сообщения в непонятно какой поток. Используйте DispatcherTimer, он выполняет события в главном потоке: var dt = new DispatcherTimer(); dt.Tick += (s, e) => { dt.Stop(); var hwnd = new WindowInteropHelper(this).Handle; FlashWindow(hwnd, true); }; dt.Interval = TimeSpan.FromSeconds(5); dt.Start();

воскресенье, 8 марта 2020 г.

Правильно ли внутри VM хранить UserControl?

#c_sharp #net #wpf #mvvm


У меня есть главная VM которая хранит 2 UserControl и VM-ки к ним. Смена UserControl
происходит благодаря свойству CurrentUserControl которое привязывается к вьюшке. Насколько
правильно хранить UserControl внутри ViewModel и как лучше это делать со стороны архитектуры
MVVM? 

Также для смены одного UserControl на другой я в каждую VM-ку передаю ссылку на главную
VM и она через эту ссылку их меняет. В правильности этого решения я тоже сомневаюсь. 

public class MainVm : VmBase
{
    public MainVm()
    {
        LoginAndRegisterVm = new LoginAndRegisterVm(this);
        EnterRegistrationKeyVm = new EnterRegistrationKeyVm(this);
        EnterRegistrationKeyUserControl = new EnterRegistrationKey();
        EnterRegistrationKeyUserControl.DataContext = EnterRegistrationKeyVm;
        LoginUserControl.DataContext = LoginAndRegisterVm;
        CurrentUserControl = LoginUserControl;
    }
    public LoginAndRegisterVm LoginAndRegisterVm { get; set; }
    public EnterRegistrationKeyVm EnterRegistrationKeyVm { get; set; }
    public UserControl LoginUserControl { get; set; } = new LoginPage();
    public UserControl EnterRegistrationKeyUserControl { get; set; }

    private UserControl _currentUserControl;
    public UserControl CurrentUserControl
    {
        get { return _currentUserControl; }
        set { _currentUserControl = value; NotifyPropertyChanged(); }
    }
}


Часть Xaml :



    


Ответы

Ответ 1



С точки зрения MVVM — нет. VM вовсе не должна знать и иметь представление о View, о том, показана ли данная VM в данный момент на экране, и если да, то как и во скольких местах. Думаю, что правильный паттерн для вашего случая такой: Вы заводите две вложенные VM: для вашего случая это, судя по всему, LoginAndRegisterVm и EnterRegistrationKeyVm. Заводите в MainVm свойство CurrentPart типа VmBase. В него кладите LoginAndRegisterVm или EnterRegistrationKeyVm, как вам нужно . Во View прописываете привязки: В основном XAML пишете просто Привязка DataContext по идее произойдёт автоматически. Вопрос правильности изменения одного VM-объекта другим не регулируется MVVM. Применяйте тот паттерн, который вам кажется более правильным.

Отображение процесса происходящего внутри функции

#c_sharp #wpf #mvvm


Не знаю как точно сформулировать вопрос.

Есть функция, которая выполняет какие-то действия. Она находится в "библиотеке классов".
Я подключаю эту библиотеку к своему приложению. Запускаю эту функцию и хочу, чтобы
в ProgressBar отображался ход выполнения этой процедуры из библиотеки. Как это правильно
реализовать в подходе MVVM?

UPD: Вот пример по методу Vlad:
Функция в библиотеке:

using System;

namespace ClassLib
{
    public static class Service
    {
        public static void Foo(IProgress progress)
        {
            progress.Report(1);    
            string s;
            for (int i = 1; i <= 99; i++)
            {                    
                for (int j = 0; j <= 50000; j++)
                     s = j.ToString();                    
                progress.Report(i);
            }    
            progress.Report(100);
        }
    }
}


ViewModel:

public class MainWindowViewModel: INotifyPropertyChanged
    {
        private int _progr;
        public int Progress {
            get { return _progr; }
            set { _progr = value;OnPropertyChanged("Progress"); } }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name) =>            
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));  
         


        public void StartFoo()
        {
            Service.Foo(new Progress(persent => Progress = persent));
        }

    }


View:
XAML


        
    
...



c# code:

private void button_Click(object sender, RoutedEventArgs e)
{
    MainWindowViewModel vm = (MainWindowViewModel)this.DataContext;
    vm.StartFoo();
}


Процесс отображается так : 0 пауза 100 %.
    


Ответы

Ответ 1



Я бы посоветовал в следующее: 1.В библиотечный метод добавить параметр типа IProgress. public class Service { public void Foo(IProgress progress) { progress.Report(1); DoSomeWork(); progress.Report(100); } } 2.В модель представления добавить свойство Progress и обработчик изменения прогресса public class ViewModel { public int Progress { get; set; } // при установке значения вызывает PropertyChanged private void HandleProgressChanged(int progress) { Progress = progress; } private void StartFoo() { Service.Foo(new Progress(HandleProgressChanged)); } } 3.В представлении привязаться к свойству Progress. UPD Ну да. Я слишком упростил пример. Ваш код синхронный. Он выполняется в UI-потоке. Поэтому подвисает интерфейс окна. Его нужно запускать асинхронно: сделать библиотечный метод async или запускать его через Task.Run. Например, так: public static async Task Foo(IProgress progress) { progress.Report(1); string s; for (int i = 1; i <= 99; i++) { for (int j = 0; j <= 50000; j++) s = j.ToString(); await Task.Delay(1);// это просто имитация бурной деятельности progress.Report(i); } progress.Report(100); } public async Task StartFoo() { await Service.Foo(new Progress(persent => Progress = persent)); } private async void button_Click(object sender, RoutedEventArgs e) { MainWindowViewModel vm = (MainWindowViewModel)this.DataContext; await vm.StartFoo(); } Еще я бы посоветовал использовать команды, а не обработчики нажатия кнопок.

суббота, 7 марта 2020 г.

Как в WPF MVVM сделать привязку данных к ComboBox

#wpf #mvvm #combobox


Здравствуйте. Столкнулся с проблемой. Делаю приложение на WPF с использованием паттерна
MVVM. Работу с базой данных осуществляю с помощью Entity Framework. Есть 2 таблицы:
Сотрудники(ФИО, id должности) и Должности(id, название). Я хочу, чтобы на форме с сотрудниками,
когда я выбираю одного из них, выводилась вся информация о нём (ФИО, должность и тд).
Это я осуществил, создав класс EmployeesViewModel:

class EmployeesViewModel : INotifyPropertyChanged
{
    OpticsEntities db;
    private Employee selectedEmployees;
    public ObservableCollection Employees { get; set; }


    public Employee SelectedEmployees
    {
        get { return selectedEmployees; }
        set
        {
            selectedEmployees = value;
            OnPropertyChanged("SelectedEmployees");
        }
    }

    public EmployeesViewModel()
    {
        db = new OpticsEntities();
        db.Employees.Load();
        Employees = db.Employees.Local;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged([CallerMemberName]string prop = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
    }

    private RelayCommand addCommand;
    private RelayCommand saveCommand;
    private RelayCommand removeCommand;
    private RelayCommand changeCommand;

    public RelayCommand AddCommand
    {
        get
        {
            return addCommand ??
                (addCommand = new RelayCommand(obj =>
                {
                    Employee employees = new Employee();
                    Employees.Insert(0, employees);
                    SelectedEmployees = employees;
                }));
        }
    }

    public RelayCommand SaveCommand
    {
        get
        {
            return saveCommand ??
                (saveCommand = new RelayCommand(obj =>
                {
                    Employee employees = obj as Employee;
                    if (employees != null)
                    {
                        db.Employees.Add(employees);
                        db.SaveChanges();
                    }
                }));
        }
    }

    public RelayCommand ChangeCommand
    {
        get
        {
            return changeCommand ??
                (changeCommand = new RelayCommand(obj =>
                {
                    Employee employees = obj as Employee;
                    if (employees != null)
                    {
                        var changeEmployees = db.Employees.Find(employees.id);
                        employees = changeEmployees;
                        db.SaveChanges();
                    }
                }));
        }
    }
}


и представление:

    
        
            
                
                    
                
            
        
    

    
        
        
    

    
        
        
            
                
                    
                        
                    
                
            
        
        
        
            
                
                    
                        
                    
                
            
        
        
        
            
                
                    
                        
                    
                
            
        
    


Всё отлично работает. Можно добавить нового сотрудника и сохранить.


Но проблема вот в чем. Поле "Должность" связано с таблицей "Должности" по внешнему
ключу (id). И я хочу, чтобы вместо текстового поля с id должности был ComboBox, в котором
бы находились названия всех должностей из таблицы "Должности". По умолчанию в нем была
бы та должность, которой соответствует сотрудник, но можно было бы открыть ComboBox
и присвоить ему другую. После нажатия на Save всё сохранилось бы в базе. 

Максимум, что я смог сделать, это создать ComboBox, в котором будут отображаться
эти должности. Но для этого пришлось сделать еще одну коллекцию во ViewModel и обращаться
уже к ней в ItemsSource. Естественно, что бы я в ней не выбрал, никакой связи с сотрудником
не будет, так как данные не связаны получается. 

Как мне сделать, чтобы в ComboBox отображались должности из таблицы Должности, и
при выборе одной из них она бы присваивалась полю Должность у Сотрудника? Напомню,
что таблицы связаны внешним ключом по id должности.
    


Ответы

Ответ 1



Тут достаточно много вариантов реализации, красивых и не очень, но если попробовать сделать с участием дополнительной коллекции моделей представления для каждой должности, как вы описали. Пример искусственный, написан для демонстрации подхода к использованию тонкостей привязки ComboBox к данным, ни в коем случае не является примером реализации правильного MVVM. Модели сущностей предметной области: public class Employee { public string Name { get; set; } public int PostID { get; set; } } public class Post { public int ID { get; set; } public string Name { get; set; } } Модели представления: public class EmployeeViewModel : NotificationObject { public EmployeeViewModel(Employee model, IEnumerable posts) { Model = model; var postViewModels = new List(); foreach (var post in posts) { postViewModels.Add(new PostViewModel(post)); } PostViewModels = postViewModels; } public Employee Model { get; set; } public IEnumerable PostViewModels { get; private set; } } public class PostViewModel : NotificationObject { public PostViewModel(Post model) { Model = model; } public Post Model { get; set; } } Класс NotificationObject просто реализует INotifyPropertyChanged. В DataContext окна попадает EmployeeViewModel. Разметка: Пример работающий, в Combobox будут отображаться названия должностей, при этом в объект Employee при выборе в ComboBox будет присваиваться валидный идентификатор должности. У ComboBox есть несколько крайне полезных свойств. DisplayMemberPath - путь к свойству сущности в DataContext элемента списка ComboBox, который будет отображаться в выпадающем списке и в основном теле ComboBox. SelectedValue - наравне с SelectedItem можно биндиться к свойству SelectedValue, значение которого берётся по определённому пути из SelectedItem. SelectedValuePath - путь от SelectedItem, по которому берётся SelectedValue. В моём примере при выборе в ComboBox в его свойство SelectedItem попадает PostViewModel, а в свойство SelectedValue попадает уже искомый идентификатор должности. Это свойство и забиндено на PostID сущности Employee. Надеюсь, принцип понятен и удастся перенести это на ваш случай.

среда, 4 марта 2020 г.

Изменение видимости элемента управления при обновлении свойства во ViewModel

#c_sharp #wpf #mvvm #события


К примеру, есть класс User. Есть ViewModel, в которой есть свойство LastUser. Свойство
периодически обновляется, генерируя NotifyPropertyChanged. На форме, поверх остальных
элементов, лежит невидимая панель. При обновлении свойства, необходимо показать панель
на несколько секунд, а затем спрятать. Как можно реализовать такое поведение? Есть
ли какой-нибудь EventTriger или DataTrigger?
    


Ответы

Ответ 1



Наиболее простым видится вынести в VM отдельное свойство для видимости панели и сделать метод, который запускать в сеттере LastUser: private async void ShowDetails() { IsVisiblePanel = true; await Task.Delay(TimeSpan.FromSeconds(1)); IsVisiblePanel = false; } Если не хотите делать это в VM - можно сделать во View, подписавшись вручную на PropertyChanged и делая тоже самое. Другой вариант, немного более замороченый - задействовать анимацию. Давайте будем действовать через прозрачность, у меня вот такая панель: Обратите внимание, у панели установлен DataContext, это важно, мы этим воспользуемся в дальнейшем. Давайте добавим в панель стиль и определим триггер, который будет скрывать панель когда она полностью прозрачна: Хорошо, а теперь управлять прозрачностью будет анимация, для этого запустим ее в EventTrigger Чтобы событие Binding.TargetUpdated возникало нужно его включить: DataContext="{Binding SelectedUser, NotifyOnTargetUpdated=True}" Теперь надо отключить панель в обычном режиме: Opacity="0" Ну и можно сделать анимацию посимпатичнее: Полный код панели, на всякий случай: PS: как раз вчера появилась статья на Хабре, в которой описаны возможные замены подписке на Binding.TargetUpdated. Получается всё можно сделать гораздо проще используя PropertyChangedTrigger из пакета Microsoft.Expression.Interactions

понедельник, 24 февраля 2020 г.

Повесить команду на событие

#c_sharp #wpf #xaml #mvvm


Подскажите пожалуйста как можно повесить команду (ICommand) из ViewModel на событие
Window.Closed? Ради соблюдения принципов MVVM не хочется пользоваться обычным обработчиком.
    


Ответы

Ответ 1



Используйте EventTrigger в сочетании с InvokeCommandAction из неймспейса Windows.Interactivity:

суббота, 15 февраля 2020 г.

WPF&MVVM: Библиотека System.Windows.Interactivity больше не доступна?

#c_sharp #wpf #xaml #mvvm


В рамках урока по паттерну MVVM для приложений WPF мне нужно было добавить библиотеку
System.Windows.Interactivity.dll через Reference Manager. Я её там не обнаружил (версия
Visual Studio - 2017). На скриншоте ниже - всё, что есть в результатах поиска, начинающееся
с System.Windows:



Я подумал, что возможно данная библиотека уже встроена в новую версию проекта WPF
(хотя в списке уже подключённых ссылок её тоже нет), и когда добавил нижеследующий
код в ApplicationViewModel.cs, никаких ошибок пока что не было отображено средой разработки:

private RelayCommand doubleCommand;
public RelayCommand DoubleCommand {
    get {
        return doubleCommand ??
        (doubleCommand = new RelayCommand(obj => {
            Phone phone = obj as Phone;
            if (phone != null) {
                Phone phoneCopy = new Phone {
                    Company = phone.Company,
                    Price = phone.Price,
                    Title = phone.Title
                };
                Phones.Insert(0, phoneCopy);
            }
        }));
    }
}


Однако, когда я добавил код XAML, то среда выдала сообщение о том, что в пространстве
имён clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity
не существует Interaction, EventTrigger и InvokeCommandAction:





    






Не знаю, каким образом это связано с библиотекой System.Windows.Interactivity, но
как следует решить данную проблему?
    


Ответы

Ответ 1



Требуемая библиотека является частью Microsoft Expression Blend 4 SDK и не включена в стандартную поставку, но вы всегда можете подключить ее из NuGet Gallery: System.Windows.Interactivity.WPF by Microsoft Пользователи Visual Studio 2017 могут установить Microsoft Blend SDK в систему с помощью Visual Studio Installer, для этого на вкладке "Отдельные компоненты" ("Individual components") в разделе "Пакеты SDK, библиотеки и платформы" ("SDKs, libraries, and frameworks") необходимо установить галочку напротив пункта "Пакет SDK для Microsoft Blend для Visual Studio для платформы .NET" ("Blend for Visual Studio SDK for .NET") и нажать кнопку "Изменить". После этого, библиотека System.Windows.Interactivity станет доступна из Reference Manager в разделе Assemblies/Extensions Также, Blend SDK можно загрузить и установить в систему из центра загрузок Microsoft: 3.5, 4.0

пятница, 14 февраля 2020 г.

Команда на DoubleClick по элементу ListView

#c_sharp #wpf #mvvm


Есть


    
        
            
                
                
                    
                
            
        
    



Где StudentList - коллекция студентов, SelectedStudent - конкретный выбранный студент.

Необходимо по двойному нажатию на ListView, вызвать команду EditStudent. Команды
умею привязывать только к Button.
Как поступить в этом случае?
    


Ответы

Ответ 1



Давайте воспользуемся такой штукой как InputBindings: ... Это уже работает, но у всех ItemsControl есть нюанс - по умолчанию они не растягивают свои элементы, это можно увидеть, если задать StackPanel любой цвет в Background, поэтому команда будет вызываться только при клике четко по тексту элемента. Исправить это можно, подкорректировав стиль ListViewItem: ... Ну и нужно еще что-то сделать с отступами, так как то что у вас сейчас StackPanel Margin="10" вам скорее всего не понравится, например можно обернуть всё в прозрачный Border (InputBindings теперь переезжают в него, в StackPanel они больше не нужны): ... Вот тогда между элементами будут визуальные отступы, но команда будет отрабатывать по клику в любом месте элемента

Ответ 2



private void ListView_MouseDoubleClick_1(object sender, MouseButtonEventArgs e) { EditStudent(); } Выделите listview в конструкторе; Перейдите во вкладку "свойства"; Кликните на кнопку с картинкой молнии в верхнем правом углу, ее название "Обработчики событий выбранного элемента", в разных студиях может называться по разному; Найдите событие с именем MouseDoubleClick, в пустое поле кликните 2 раза и студия все сделает за Вас;

Ответ 3





Создание экземпляра model во viewmodel

#c_sharp #wpf #mvvm


Можно ли создавать экземпляр model внутри viewmodel ? 
Допустим в целях использования методов модели во viewmodel. 
Не нарушает ли это паттерн mvvm ? 
И , если можно, пример, пожалуйста, как это правильно реализовать 
    


Ответы

Ответ 1



Можно. В простейшем случае именно так и нужно делать. V "запрашивает" у VM некий интерфейс, а VM с помощью средств, предоставляемых M реализует этот интерфейс. Под интерфейсом я здесь принимаю набор свойств и команд, которые V хочет предоставить конечному пользователю. В более сложных случаях, если у вас есть несколько моделей, VM может получать экземпляр M в качестве параметра конструктора, например.

Ответ 2



Можно. И создавать, и использовать. Согласно MVVM, VM имеет право знать о M и управлять ей. По поводу примера — загляните, например, сюда.

понедельник, 10 февраля 2020 г.

Команда кнопки “ОК” дочернего окна, закрывающего его. MVVM WPF

#c_sharp #wpf #mvvm


Создание и показ дочернего окна из MainViewModel:

    public ICommand OpenWindow_AddNewBid_Command => new RelayCommand(p =>
OpenWindow_AddNewBid());
    private void OpenWindow_AddNewBid()
    {            
        var wnd = new AddNewBidWindow();           
        if (wnd.ShowDialog() == true)
        {
            AddNewBidViewModel vm = (AddNewBidViewModel)wnd.DataContext;
            AddNewBidToCollection(vm.CreatedBid);
        }
    }


В ViewModel дочернего окна есть команда, которая привязана к кнопке OK.

    public ICommand AddNewBidCommand => new RelayCommand(p => AddNewBid(),
                                                                 p => IsValid);


XAML:




Не могу понять, как сделать, что бы выполнилась команда привязанная к кнопке и окно
закрылось?
    


Ответы

Ответ 1



Самый простой вариант, не нарушающий принципы MVVM. View: public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var vm = new MainViewModel(); this.DataContext = vm; vm.CloseHandler += (sender, args) => this.Close(); } } ViewModel: public class MainViewModel : BindableBase { public EventHandler CloseHandler; public ICommand AddNewBidCommand => new RelayCommand(action => { // ... var handler = CloseHandler; if (handler != null) handler.Invoke(this, EventArgs.Empty); }, canExecute => true); } Вариант с передачей экземпляра окна в команду через CommandParameter возможен, но будет нарушать принципы MVVM.

Ответ 2



Изящный вариант закрытия окна из VM в духе MVVM (декларативный и через биндинги) Вспомогательный класс public static class DialogCloser { public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached( "DialogResult", typeof (bool?), typeof (DialogCloser), new PropertyMetadata(DialogResultChanged)); private static void DialogResultChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { var window = d as Window; if (window != null) { window.DialogResult = e.NewValue as bool?; if (window.DialogResult != null) window.Close(); } } public static void SetDialogResult(Window target, bool? value) { target.SetValue(DialogResultProperty, value); } } VM class MyViewModel { private bool? _dialogResult; public bool? DialogResult { get { return _dialogResult; } protected set { _dialogResult = value; OnPropertyChanged("DialogResult"); } } public ICommand OkCommand {get;}=new RelayCommand(Save); private void Save() { .... DialogResult = true; } } XAML При выставлении у VM DialogResult=true/false форма закроется и у нее будет установлен DialogResult, так что проверка результата будет работать if(view.ShowDialog()==true)..

среда, 5 февраля 2020 г.

MVVM для начинающих

#c_sharp #wpf #mvvm


Пытаюсь вникнуть в суть MVVM, рассматривая простейшие примеры и мне непонятны некоторые
вещи,такие как:

public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName]string prop = "")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }


Также непонятно как работает интерфейс INotifyPropertyChanged
Может ли кто-нибудь все это объяснить?
P.S. на MSDN и других сайтах был и читал,но понятнее не стало
    


Ответы

Ответ 1



Попробую объяснить. Между View(графический интерфейс программы (кнопки, текстовые поля, и проч.)) с помощью так называемого байдинга (кто-то называет биндинг, хотя это неправильное произношение) устанавливается связь со свойствами во ViewModel. Например с текстовым полем ввода мы связываем свойство (ViewModel) - public string Name { get; set; } с помощью записи в XAML (View) /// Имя скачиваемого видео файла /// private string _NameFileVideo; public string NameFileVideo { get { return _NameFileVideo; } set { if (_NameFileVideo == value) return; _NameFileVideo = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(NameFileVideo))); } }

Ответ 2



Подозреваю, что основное, что вас пугает в этом коде - события. Не уверен, что получится понятно объяснить, но попробую провести аналогию. Каждый день в мире происходит множество событий, как глобальных так и локальных. Но о большинстве из них вы никогда не узнаете по той простой причине, что они вам не интересны. Вы хотите узнавать лишь те новости, которые важны для вас и ваших близких. Таким образом ставя "фильтр" вы подписываетесь только на интересующие вас события - например, на новости космонавтики. Тоже самое делает элемент управления вашего приложения через интерфейс INotifyPropertyChanged. Другими словами для элемента управления это и есть фильтр на тематику новостей от объекта. Если объект не реализовал интерфейс - значит он ему не интересен. Что касается привязки данных (Binding)...если проводить аналогию дальше, то это уже будет более конкретный фильтр (например, вас интересуют только новости с Марса). Так и элемент управления интересуется актуальными данными только от тех свойств объекта с которыми он непосредственно работает и он ничего не знает и не хочет знать обо все остальном что происходит в вашем классе. Когда вы изменяете значение свойства в коде - это уже событие. Но ни элемент управления, ни кто-либо другой об этом ничего не знает. Именно поэтому когда вы хотите "опубликовать" новость вы пишите: OnPropertyChanged("Название свойства") и тот элемент управления, который подписался на получение новостей об этом свойстве отреагирует должным образом. Атрибут CallerMemberName просто позволяет получить имя метода или свойства объекта, который вызывает метод. В данном случае используется, чтобы избежать возможных ошибок связанных с неправильным названием свойства для обновления.

пятница, 31 января 2020 г.

Смысл от перегрузки метода SetProperty с Action в который нельзя передать объект

#c_sharp #mvvm #prism


В классе BindableBase есть 2 перегрузки метода

bool SetPropetry(ref T storage, T value, string propertyName = null);
bool SetPropetry(ref T storage, T value, Action onChanged, string propertyName
= null);


Не вижу в чем смысл данной перегрузки? Если это перегрузка для того что бы к примеру
повесить callback на сторонний View или ViewModel, то в чем смысл передавать Action,
если мне необходимо сослаться к примеру на ссылку какого то объекта текущей ViewModel,
или передать данные для валидации Model?

Простейшие перегрузки методов которые мне необходимы:

protected virtual bool SetProperty(ref T storage, T value, Action onChanged,
T2 obj,
    [CallerMemberName] string propertyName = null)
{
    if (!SetProperty(ref storage, value)) return false;
    onChanged.Invoke(obj);
    return true;
}

protected virtual bool SetProperty(ref T storage, T value, Action onChanged,
    ref T2 obj, [CallerMemberName] string propertyName = null)
{
    if (!SetProperty(ref storage, value)) return false;
    onChanged.Invoke(obj);
    return true;
}



  Q: Почему возник такой вопрос?
  
  A: До этого пользовался исключительно Catel, в котором изначально было описана
одна реализация установки значений полям, и оповещении подписчиков.


P.S. Я знаю что могу самостоятельно сделать свою перегрузку в которую запихну этот
самый Action, Но я все же не понимаю зачем было так делать?
    


Ответы

Ответ 1



Смотрите, никаких проблем я тут не вижу. Проблемы в том, чтобы «засунуть» дополнительные параметры в Action, нет: вы просто делаете замыкание по нужным параметрам (например, () => ProcessOnPropertySet(this)). А вот на стороне фреймворка проблемы есть, потому что непонятно, какие именно параметры вам нужно передать. Вы хотите весь объект? Вы хотите новое значение свойства? Вы хотите проверить и возможно изменить значение свойства? Вы хотите отправить событие? Всё это можно легко выразить через Action при помощи замыкания. А если мы хотим избавиться от замыкания, то фреймворку нужно заранее угадать, какие параметры вам понадобятся. И самостоятельно определить нужную вам перегрузку. А это не так просто, потому что у каждого проекта свои, специфические требования. Поэтому универсальное решение с Action вполне катит.

Ответ 2



Данный вопрос следует задавать разработчикам Prism, но никак не в этом комьюнити. Не бывает идеальных API, которые бы устраивали всех. Тем не менее, это явно не худший пример, так как у тебя остаётся возможность реализовать собственную обёртку и передать менее общий делегат. Можно взглянуть на классический подход WindowsForms-приложений, в которых коллбеки строго ограничены сигнатурой (Object sender, EventArgs args). Легко найти такие варианты использования, в которых передаётся this, null, 'new EventArgs()' и т.д. И люди точно так же задаются вопросом - зачем так было делать? Prism наследует принципы .NET Framework и предоставляет минимально достаточный API, который программисты могут расширять для своих нужд. В данном случае такая возможность есть.

среда, 29 января 2020 г.

Как анимировать смену одного контрола на другой?

#c_sharp #net #wpf #mvvm


В зависимости от привязанной к ContentPresenter Vm-ки, на экране отображаются разные
UserControl. Можно ли как-то отловить момент смены одного контрола на другой, чтобы
санимировать этот переход?


        
            
                
            
            
                
            
        


    


Ответы

Ответ 1



Окей, комментарий @Squidward @Athari на самом деле правильный: в Mahapps.Metro есть нужный контрол с несколькими предопределёнными анимациями. Но давайте напишем такой контрол самостоятельно. Перед нами стоят две проблемы. Во-первых, мы должны запомнить старый контент перед тем, как он исчезнет, чтобы правильно показать его на время анимации. Во-вторых, для показа одновременно и старого, и нового контента нам нужны два ContentPresenter'а. Для этого воспользуемся промежуточным контролом, который будет содержать эти самые два ContentPresenter'а. Итак, создаём новый контрол, назовём его AnimatableContentPresenter. Мы создаёт custom control, а не UserControl. (Это делается через Проект → Add → New Item... → WPF → Custom Control (WPF) в Visual Studio.) Для начала, код с комментариями. // объявляем, что в шаблоне должна быть предоставлена анимация, которую мы запустим [TemplatePart(Name = "PART_Animation", Type = typeof(Storyboard))] public class AnimatableContentPresenter : Control { static AnimatableContentPresenter() { DefaultStyleKeyProperty.OverrideMetadata( typeof(AnimatableContentPresenter), new FrameworkPropertyMetadata(typeof(AnimatableContentPresenter))); } Storyboard animation; // текущая анимация bool isAnimationRunning = false; #region dp object Content, on change OnContentChanged public object Content { get { return (object)GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } } public static readonly DependencyProperty ContentProperty = DependencyProperty.Register( "Content", typeof(object), typeof(AnimatableContentPresenter), new PropertyMetadata(OnContentChangedStatic)); static void OnContentChangedStatic( DependencyObject d, DependencyPropertyChangedEventArgs e) { var self = (AnimatableContentPresenter)d; self.OnContentChanged(e.OldValue, e.NewValue); } #endregion #region dp object PreviousContent public object PreviousContent { get { return (object)GetValue(PreviousContentProperty); } set { SetValue(PreviousContentProperty, value); } } public static readonly DependencyProperty PreviousContentProperty = DependencyProperty.Register( "PreviousContent", typeof(object), typeof(AnimatableContentPresenter)); #endregion // когда Content поменяется... void OnContentChanged(object oldContent, object newContent) { if (isAnimationRunning) animation?.Stop(); // ... запомним старый Content в PreviousContent PreviousContent = oldContent; // и перезапустим анимацию if (animation != null) { animation.Begin(); isAnimationRunning = true; } } // при появлении шаблона, вычитаем из него анимацию public override void OnApplyTemplate() { base.OnApplyTemplate(); if (animation != null) animation.Completed -= OnAnimationCompleted; if (isAnimationRunning) { // TODO: начать новую анимацию там, где предыдущая завершилась? animation?.Stop(); } animation = (Storyboard)Template.FindName("PART_Animation", this); if (animation != null) // подпишемся на завершение анимации animation.Completed += OnAnimationCompleted; } // по окончанию анимации... private void OnAnimationCompleted(object sender, EventArgs e) { // выбросим старый контент PreviousContent = null; // сбросим эффект анимации animation.Remove(); isAnimationRunning = false; } } Теперь нам нужен шаблон в Themes\Generic.xaml (его вам уже, скорее всего, положил мастер создания нового контрола). Отлично, большая часть работы готова. Теперь нам нужно представить это как стиль для чего-нибудь — например, для ContentControl'а. (Это штука наподобие ContentPresenter'аб немного более высокоуровневая.) Кладём в нашем окне (или ресурсах приложения) стиль: Испытываем. Получаем вот такую анимацию:

Ответ 2



Это надо делать DataTrigger + StoryBoard (вот тут пример есть). Цепляешься в твоем случае к свойствам IsLoginFineshed (к примеру) и на него цепляешь анимацию.

MVVM WPF смена DataTemplate если список пуст

#c_sharp #wpf #mvvm


есть ListBox




в нем из базы загружаются автомобили.
как сделать чтобы если база пуста(удалены все элементы или еще не заполнялась) ListBox
заменялся на это



    


Ответы

Ответ 1



Можно сделать с помощью DataTrigger без дополнительных свойств и конверторов: ListBox и Label должны находится в одном Grid(или хотя бы в одной ячейке) чтобы происходило наложение друг на друга.

Ответ 2



Сначала напишем конвертер, который является отражением BooleanToVisibilityConverter: public class InverseBooleanToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (bool)value ? Visibility.Collapsed : Visibility.Visible; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } После этого дополним модель представления признаком наличия автомобилей: class ViewModel { ... public bool HasAuto { get { return Auto.Any(); } } } После этого добавим обработку видимости в представление: Остается только вовремя обновлять свойство HasAuto путем вызова PropertyChanged. Это можно сделать подписавшись на событие изменения коллекции Auto. Или можно вызывать событие PropertyChanged при загрузке, добавлении и удалении элементов из коллекции. Тогда подписка на событие изменения состава коллекции не понадобится. UPD Ну или можно воспользоваться триггерами (свойство HasAuto все равно понадобится)

Ответ 3



1. Объявите: public ObservableCollection Auto 2. В разметке: 3. Аналогично Auto (только поменять Hidden/Visible)

пятница, 24 января 2020 г.

WPF MVVM - Анимация перехода между представлениями

#wpf #анимация #mvvm #mvvm_light


Разрабатываю проект на WPF с использованием паттерна MVVM. Использую MVVM Light.
Каким образом возможно реализовать анимацию переходов от одного представления к другому?
К примеру, плавное "затухание" одного и появление другого.
    


Ответы

Ответ 1



Не знаю как это делается в MVVM Light, я обычно определяю StoryBoard внутри нужного элемента c нужной анимацией, привязываю триггер анимации к нужному свойству элемента, через который реализован переход (скажем, Button.IsPressed). Все работает. Здесь поподробнее https://msdn.microsoft.com/ru-ru/library/ms742868(v=vs.110).aspx

Можно ли использовать MVVM там, где нет data binding?

#java #swing #mvvm


На Википедии про MVVM написано:


  MVVM удобно использовать вместо классического MVC и ему подобных в тех случаях,
когда в платформе, на которой ведётся разработка, присутствует «связывание данных».



Объясните, пожалуйста, что такое связывание данных? В сети совсем
мало информации. Я могу привести свои догадки на примере Delphi. Там
можно поместить на форму DBGrid, и данные из базы будут попадать в
него. То есть, они будут как бы привязаны к нему. Но, если, конечно, в
настройках правильно все прописать. Это и есть связывание данных?
Тогда, как я понимаю, в Java Swing нет никакого data binding. Можно ли там использовать
MVVM? В принципе, можно, наверное, просто самому написать функции bind и backBind,
в которых нужные данные туда-сюда передавать?
Если можно, то стоит ли? Или лучше MVC или MVP? Понимаю, что так однозначно сказать
нельзя, от ситуации, наверное, зависит. У меня десктопный проект с небольшой базой
данных (JDBC и MySql), но графический интерфейс довольно объемный и сложный, с большим
количеством функций. Предметная область для моего приложения — сложная алгоритмическая
задача (задача о ранце, кому интересно). Подскажите пожалуйста, что в моей ситуации
лучше использовать — MVC, MVP или MVVM (опыта ни в чем из этого не имею).

    


Ответы

Ответ 1



Шаблон MVVM больше ориентирован на технологии .Net от Microsoft, в частности под язык разметки XAML. В WPF или Silverlight привязка данных имеет большое значение, трудно представить серьёзное приложение без механизма привязок (bindings). Есть альтернатива на Java - платформа JavaFX. Привязка подразумевает взаимодействие двух объектов: источника и приемника. Объект-приемник создает привязку к определенному свойству объекта-источника. В случае модификации объекта-источника, объект-приемник также будет модифицирован (поведение зависит от типа привязки). Всего есть 4 типа привязок. • OneWay: свойство объекта-приемника изменяется после модификации свойства объекта-источника. • OneTime: свойство объекта-приемника устанавливается по свойству объекта-источника только один раз. В дальнейшем изменения в источнике никак не влияют на объект-приемник. • TwoWay: оба объекта - применки и источник могут изменять привязанные свойства друг друга. • OneWayToSource: объект-приемник, в котором объявлена привязка, меняет объект-источник. • Default: по умолчанию (если меняется свойство TextBox.Text, то имеет значение TwoWay, в остальных случаях OneWay). Пример Самому написать механизм с такой функциональностью будет не просто. Смотрите на платформу JavaFX, если выберите MVVM и Java. Если .Net то WPF или универсальные приложения под windows 10. Делая Web приложение посоветую Spring MVC.

Ответ 2



На счет какой того какой паттерн использовать я особо ничего сказать не могу. Если нужен красивый, высоконагруженный интерфейс, я бы использовал JavaFX, а там классический паттерн MVC. А на счет связывания данных, такую фичу я встречал в NetBeans'е (как в старом добром дельфи - "без единной строчки кода"), там есть связывание с помощью своих библиотек (они потом прикрепляются к проекту).

Принцип, структура и использование WPF фреймворка mvvmlight

#c_sharp #wpf #mvvm #mvvm_light


Пытаюсь разобраться с MVVM Light. И чем дальше, тем страшнее. Вопросы возникают как
снежный ком. В сети не нашел ни одной статьи, где бы простым русским языком объяснялся
бы mvvm-light и его практическое применение. Ниже я приведу места и моменты, которые
вызывают у меня непонимание.

Установил на VisualStudio 2015 MVVM Light, создал новый проект MvvmLight(WPF451).
В проекте создались следующие файлы и папки:

[Design]
    DesignDataService.cs
[Model]
    DataItem.cs
    DataService.cs
    IDataService.cs
[Skins]
    MainSkin.xaml
[ViewModel]
    MainViewModel.cs
    ViewModelLocator.cs
App.xaml
App.xaml.cs
MainWindow.xaml
MainWindow.xaml.cs


В файле App.xaml видим следующее .
Как было выяснено здесь, это не подходит по ряду причин, а поэтому нужно удалить этот
атрибут и в перегруженном методе OnStartup класса App создать экземпляр необходимой
модели представления и установить её как DataContext, примерно так:

public partial class App : Application {

    MainViewModel mainVM = new MainViewModel();

    static App() {
        DispatcherHelper.Initialize();
    }

    protected override void OnStartup(StartupEventArgs e) {
        base.OnStartup(e);
        new MainWindow() { DataContext = mainVM }.Show();
    }
}


, но при использовании в созданном проекте mvvm-light класс MainViewModel принимает
в конструкторе IDataService dataService. В проекте так и не нашел каким образом создается
этот dataService. Для каждой ли модели представления нужно этот dataService создавать?
Как, когда и где он используется?

Также в созданном проекте присутствует класс ViewModelLocator. В комментарии класса
указан пример использования:

/*
In App.xaml:

    


In the View:
DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"
*/


, но вроде же как явное указание DataContext в XAML неправильно?! Или я не прав?!
Для чего и как он используется?  

В code-behind представления MainWindow к событию Closing добавляется очистка ViewModelLocator:


Closing += (s, e) => ViewModelLocator.Cleanup();


, но в ViewModelLocator ничего не добавляется. Или это происходит где-то далеко за
кулисами?!

В файле MainWindow.xaml существует привязка к ресурсу:


    
        
            
        
    



Как я понял, это используется только для работы с визуальным редактором и в рабочей
версии никакой роли не играет. Поправьте меня, если я не прав.



В сети присутствует множество примеров mvvm-light, но они все сводятся к слепому
следованию инструкциям, без пояснения для чего это делается и как это в последствии
используется. Предвидя упреки об изучении мат.части, отмечу, что мне лично материал
легче усваивается на практике. И не хотелось бы колотить себе шишки, а перенять опыт
знающих людей и сразу учить как нужно делать, а не как можно. Может существует какой-либо
проект в сети, который подробно описывает использование mvvm-light фреймворка на русском?
    


Ответы

Ответ 1



Самое простое - следовать рекомендации, и делать все через ViewModelLocator. DataService создается через инъекции из IoС контейнера. Так регистрация происходит в ViewModelLocator.cs в конструкторе пишем SimpleIoc.Default.Register(); где DebugDataContext - конкретная реализация интерфейса IDataTask например база данных для дебага, а в продакшене будет с другого места браться (но это уже совсем другая история). Регистрируем VM SimpleIoc.Default.Register(); Выставляем ее на публику public MainViewModel Main => ServiceLocator.Current.GetInstance(); Все это есть уже по умолчанию, для других форм просто по аналогии. И вот когда вьюха будет обращаться к VM посредством того кода в XAML автоматически через IoC будет цепляться нужный источник данных. Вам там ответили, что вьюха не должна создавать VM. но она и не создает, создает IoC-контейнер, вьюха только запрашивает его экземпляр. Так что вам там не совсем верно ответили, без инъекций все действительно так, но в MVVM Light уже есть IoC и вся парадигма строится вокруг инъекций В коде можно тоже, если очень хочется, но лично я не вижу в этом смысла mainVM = ServiceLocator.Current.GetInstance(); Конечно всё это ИМХО, у VlaD'a свои взгляды, у меня свои, и MVVM это не святое писание, а просто идея и используют ее по разному, но по задумке создателей MVVM Light это должно делаться примерно так как я описал.