Я предлагаю отказаться от модальных диалогов 1990-х годов и вместо этого реализовать элемент управления в виде наложения (холст + абсолютное позиционирование) с привязкой к видимости к логическому обратно в виртуальной машине. Ближе к элементу управления типа ajax.
Это очень полезно:
<BooleanToVisibilityConverter x:Key="booltoVis" />
как в:
<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>
Вот как я реализовал его как пользовательский элемент управления. Нажатие на 'x' закрывает элемент управления в строке кода в коде пользовательского элемента управления позади. (Поскольку у меня есть представления в .exe и ViewModels в dll, я не расстраиваюсь по поводу кода, который управляет пользовательским интерфейсом.)
Я обдумывал подобную проблему при выяснении , как модель представления для задачи или диалогового окна должна быть похожей .
Мое текущее решение похоже на это:
public class SelectionTaskModel<TChoosable> : ViewModel
where TChoosable : ViewModel
{
public SelectionTaskModel(ICollection<TChoosable> choices);
public ReadOnlyCollection<TChoosable> Choices { get; }
public void Choose(TChoosable choosen);
public void Abort();
}
, Когда модель представления решает, что ввод данных пользователем требуется, он тянет экземпляр SelectionTaskModel
с возможным выбором для пользователя. Инфраструктура заботится о переводе в рабочее состояние соответствующего представления, которое в надлежащее время будет звонить эти Choose()
функция с выбором пользователя.
Я думаю, что обработка диалогового окна должна быть ответственностью представления, и представление должно иметь код для поддержки этого.
при изменении ViewModel - взаимодействие Представления для обработки диалоговых окон тогда ViewModel является иждивенцем на той реализации. Самый простой способ иметь дело с этой проблемой состоит в том, чтобы сделать Представление ответственным за выполнение задачи. Если это означает показывать диалоговое окно, тогда прекрасное, но могло бы также быть сообщением о состоянии в строке состояния и т.д.
, Моя точка - то, что смысл шаблона MVVM должен разделить бизнес-логику от GUI, таким образом, Вы не должны смешивать логику GUI (для отображения диалогового окна) в бизнес-слое (ViewModel).
Я боролся с той же проблемой. Я придумал способ общаться между Представлением и ViewModel. Можно инициировать отправку сообщения от ViewModel до Представления, чтобы сказать этому показывать messagebox, и это сообщит с результатом. Затем ViewModel может ответить на результат, возвращенный из Представления.
Я демонстрирую это в своем блоге:
Хороший диалог MVVM должен:
К сожалению, WPF не предоставляет эти функции. Для отображения диалога требуется вызов кода для ShowDialog (). Класс Window, который поддерживает диалоги, не может быть объявлен в XAML, поэтому его нельзя легко привязать к DataContext.
Чтобы решить эту проблему, я написал элемент-заглушку XAML, который находится в логическом дереве и передает привязку данных к Окно и ручки, показывающие и скрывающие диалог. Вы можете найти его здесь: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
Он действительно прост в использовании и не требует каких-либо странных изменений в вашей ViewModel и не требует события или сообщения. Основной вызов выглядит так:
<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />
Вы, вероятно, хотите добавить стиль, который устанавливает Показ. Я объясняю это в моей статье. Я надеюсь, что это поможет вам.
Вам следует использовать посредник для этого. Посредник является распространенным шаблоном проектирования, также известным как Messenger в некоторых его реализациях. Это парадигма типа Register / Notify, которая позволяет вашим ViewModel и Views обмениваться данными через механизм обмена сообщениями с низкой связью.
Вы должны проверить группу Disciples google WPF и просто найти Mediator. Вы будете очень довольны ответами ...
Однако вы можете начать с этого:
http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf- apps /
Наслаждайтесь!
Edit: вы можете увидеть ответ на эту проблему с помощью MVVM Light Toolkit здесь:
http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338
Я думаю, что представление может иметь код для обработки события из модели представления.
В зависимости от события / сценария, оно также может иметь триггер события, который подписывается на просмотр событий модели, и одно или несколько действий для вызова в ответ.
Потратив годы на решение этой проблемы в WPF, я наконец-то нашел стандартный способ реализации диалогов в WPF. Вот преимущества этого подхода:
Так в чем же ключ. Это DI + IoC .
1150 Вот как это работает. Я использую MVVM Light, но этот подход может быть распространен и на другие платформы:
Добавить интерфейс IDialogService в проект VM:
public interface IDialogService
{
void ShowMessage(string msg, bool isError);
bool AskBooleanQuestion(string msg);
string AskStringQuestion(string msg, string default_value);
string ShowOpen(string filter, string initDir = "", string title = "");
string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
string ShowFolder(string initDir = "");
bool ShowSettings();
}
Предоставить открытое статическое свойство типа IDialogService
в вашем ViewModelLocator
, но оставьте часть регистрации для слоя View для выполнения. Это ключ .:
public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
Добавить реализацию этого интерфейса в проект приложения.
public class DialogPresenter : IDialogService
{
private static OpenFileDialog dlgOpen = new OpenFileDialog();
private static SaveFileDialog dlgSave = new SaveFileDialog();
private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
/// <summary>
/// Displays a simple Information or Error message to the user.
/// </summary>
/// <param name="msg">String text that is to be displayed in the MessageBox</param>
/// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
public void ShowMessage(string msg, bool isError)
{
if(isError)
System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
else
System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
}
/// <summary>
/// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public bool AskBooleanQuestion(string msg)
{
var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
return Result;
}
/// <summary>
/// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
/// user clicks Save button. Returns null if user clicks Cancel button.
/// </summary>
/// <param name="filter"></param>
/// <param name="initDir"></param>
/// <param name="title"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
{
if (!string.IsNullOrEmpty(title))
dlgSave.Title = title;
else
dlgSave.Title = "Save";
if (!string.IsNullOrEmpty(fileName))
dlgSave.FileName = fileName;
else
dlgSave.FileName = "";
dlgSave.Filter = filter;
if (!string.IsNullOrEmpty(initDir))
dlgSave.InitialDirectory = initDir;
if (dlgSave.ShowDialog() == DialogResult.OK)
return dlgSave.FileName;
else
return null;
}
public string ShowFolder(string initDir = "")
{
if (!string.IsNullOrEmpty(initDir))
dlgFolder.SelectedPath = initDir;
if (dlgFolder.ShowDialog() == DialogResult.OK)
return dlgFolder.SelectedPath;
else
return null;
}
/// <summary>
/// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
/// user clicks Open button. Returns null if user clicks Cancel button.
/// </summary>
/// <param name="filter"></param>
/// <param name="initDir"></param>
/// <param name="title"></param>
/// <returns></returns>
public string ShowOpen(string filter, string initDir = "", string title = "")
{
if (!string.IsNullOrEmpty(title))
dlgOpen.Title = title;
else
dlgOpen.Title = "Open";
dlgOpen.Multiselect = false;
dlgOpen.Filter = filter;
if (!string.IsNullOrEmpty(initDir))
dlgOpen.InitialDirectory = initDir;
if (dlgOpen.ShowDialog() == DialogResult.OK)
return dlgOpen.FileName;
else
return null;
}
/// <summary>
/// Shows Settings dialog.
/// </summary>
/// <returns>true if User clicks OK button, otherwise false.</returns>
public bool ShowSettings()
{
var w = new SettingsWindow();
MakeChild(w); //Show this dialog as child of Microsoft Word window.
var Result = w.ShowDialog().Value;
return Result;
}
/// <summary>
/// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog
/// and the second string specifies the default value to be displayed in the input box.
/// </summary>
/// <param name="m"></param>
public string AskStringQuestion(string msg, string default_value)
{
string Result = null;
InputBox w = new InputBox();
MakeChild(w);
if (w.ShowDialog(msg, default_value).Value)
Result = w.Value;
return Result;
}
/// <summary>
/// Sets Word window as parent of the specified window.
/// </summary>
/// <param name="w"></param>
private static void MakeChild(System.Windows.Window w)
{
IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
var helper = new WindowInteropHelper(w) { Owner = HWND };
}
}
ShowMessage
, AskBooleanQuestion
и т. Д.), Другие специфичны для этого проекта и используют пользовательские Window
с. Вы можете добавить больше пользовательских окон таким же образом. Ключ заключается в том, чтобы сохранять специфичные для пользовательского интерфейса элементы на уровне просмотра и просто отображать возвращаемые данные, используя POCO на уровне VM . Выполните IoC-регистрацию вашего интерфейса в слое View, используя этот класс. Вы можете сделать это в конструкторе вашего основного вида (после вызова InitializeComponent()
):
SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
Вот и все. Теперь у вас есть доступ ко всем функциям диалогов на слоях VM и View. Уровень вашей виртуальной машины может вызывать эти функции так:
var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
IDialogService
в вашем тестовом проекте и зарегистрировать этот класс в IoC в конструкторе вашего тестового класса. [+1139] Microsoft.Win32
, чтобы получить доступ к диалогам открытия и сохранения. Я не учел их, потому что есть также версия этих диалоговых окон для WinForms, плюс кто-то может захотеть создать свою собственную версию. Также обратите внимание, что некоторые из идентификаторов, использованных в DialogPresenter
, являются именами моих собственных окон (например, SettingsWindow
). Вам нужно будет либо удалить их как из интерфейса, так и из реализации, или предоставить свои собственные окна. DispatcherHelper.Initialize()
в начале жизненного цикла вашего приложения. За исключением DialogPresenter
, который внедряется в слой View, другие ViewModals должны быть зарегистрированы в ViewModelLocator
, а затем открытое статическое свойство этого типа должно быть открыто для использования слоем View. Примерно так:
public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
В большинстве случаев ваши диалоги не должны иметь никакого кода для таких вещей, как привязка или установка DataContext и т. Д. Вы даже не должны передавать вещи как параметры конструктора. XAML может сделать все это за вас, например, так:
<Window x:Class="YourViewNamespace.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:YourViewProject"
xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
DataContext="{x:Static vm:ViewModelLocator.Settings}"
d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
DataContext
таким образом дает вам все преимущества времени разработки, такие как Intellisense и автозаполнение. Надеюсь, что помогает всем.
Я помню, как писать это. Я думаю, что для прямоугольной матрицы вам понадобится несколько незначительных изменений и еще одна линия непостижимой ерунды:
#include <stdio.h>
int main()
{
int x[3][4] = { 1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12};
int m = 3;
int n = 4;
for (int slice = 0; slice < m + n - 1; ++slice) {
printf("Slice %d: ", slice);
int z1 = slice < n ? 0 : slice - n + 1;
int z2 = slice < m ? 0 : slice - m + 1;
for (int j = slice - z2; j >= z1; --j) {
printf("%d ", x[j][slice - j]);
}
printf("\n");
}
return 0;
}
. Выход:
Slice 0: 1
Slice 1: 5 2
Slice 2: 9 6 3
Slice 3: 10 7 4
Slice 4: 11 8
Slice 5: 12
, чтобы кратко объяснить, как это работает, каждый ломтик - это диагональ, начиная с первого столбца, пройдя по диагонали up - вправо и заканчивается на первом ряду (изначально влево, но теперь она поменялась в результате комментариев от плаката).
Z2 говорит, сколько предметов должно быть пропущено до того, как следует напечатать первый номер. Это равно нулю для первых м ломтиков, а затем увеличивается на один для каждого оставшегося ломтика. Z1 - это сколько предметов должно быть пропущено в конце, снова начиная с нуля для ломтиков первых M и увеличением на один для остальных ломтиков.
-121--3090646-Мое нынешнее решение решает большая часть вопросов, которые вы упомянули, все это полностью абстрагировали от конкретных вещей на платформе и могут быть повторно использованы. Также я не использовал код - позади только привязку с делегатекомами, которые реализуют ICOMBAND. Диалог в основном представление - отдельное управление, которое имеет свой собственный ViewModel, и он отображается с ViewModel главного экрана, но срабатывает с использованием UI через обязательство DelagateCommand.
Видите полное решение Silverlight 4 здесь Модальные диалоги с MVVM и Silverlight 4
Некоторое время я действительно боролся с этой концепцией, когда изучал (все еще учился) MVVM. Я решил, и что, как мне кажется, уже решили другие, но мне было непонятно, вот что:
Моя первоначальная мысль заключалась в том, что ViewModel не следует позволять напрямую вызывать диалоговое окно, поскольку ей не нужно решать, как должен появиться диалог. Из-за этого я начал думать о том, как я могу передавать сообщения так же, как в MVP (например, View.ShowSaveFileDialog ()). Однако я считаю, что это неправильный подход.
Для ViewModel нормально вызывать диалог напрямую.Однако, когда вы тестируете ViewModel, это означает, что диалоговое окно либо появится во время вашего теста, либо полностью завершится неудачно (никогда не пробовал этого).
Итак, во время тестирования необходимо использовать «тестовую» версию вашего диалога. Это означает, что для любого диалога, который у вас есть, вам необходимо создать интерфейс и либо смоделировать ответ диалога, либо создать тестовый макет, который будет иметь поведение по умолчанию.
Вы уже должны использовать своего рода Service Locator или IoC, который вы можете настроить, чтобы предоставить вам правильную версию в зависимости от контекста.
Используя этот подход, ваша ViewModel по-прежнему тестируется, и в зависимости от того, как вы имитируете свои диалоги, вы можете управлять поведением.
Надеюсь, это поможет.
У меня была такая же ситуация, и я заключил MessageBox в невидимый элемент управления конструктора. Подробности в моем блоге
http://geekswithblogs.net/mukapu/archive/2010/03/12/user-prompts-messagebox-with-mvvm.aspx
То же самое можно распространить на любые модальные диалоги , управление просмотром файлов и т. д.
Интересной альтернативой является использование контроллеров которые отвечают за отображение представлений (диалогов).
Как это работает, показано в WPF Application Framework (WAF) .
Я обновил свой собственный оконный загрузчик, описанный в ответе на этот вопрос: