2015-08-13 3 views
2

У меня есть приложение WPF, работающее на Caliburn.Micro, первый подход к модели представления. Существует командный тип управления с его CommandBarView.xaml и связан CommandBarViewModel. VM командной строки содержит несколько вложенных виртуальных машин, по одному для каждого элемента управления кнопки, все из которых показывают общий интерфейс и имеют общее поведение. Команда-бар VM подвергать их таким образом они могут быть связаны с точки зрения:В Caliburn.Micro как связать действие с вложенным методом ViewModel?

public interface IWarningButtonViewModel 
{ 
    bool IsVisible { get; } 
    bool CanShowWarning { get; } 
    void ShowWarning(); 
} 

public class CommandBarViewModel : PropertyChangedBase 
{ 
    public IWarningButtonViewModel UserNotFoundWarning { get; private set; } 
    public IWarningButtonViewModel NetworkProblemWarning { get; private set; } 
    // ... initialization omitted for simplicity 
} 

Это предварительный XAML для немного CommandBarView:

<Button x:Name="UserNotFoundWarning_ShowWarning" 
     IsEnabled="{Binding UserNotFoundWarning.CanShowWarning}"> 
    ... 
    <DataTrigger Binding="{Binding UserNotFoundWarning.IsVisible}" Value="True"> 
    ... 
</Button> 

В этом случае я могу успешно привязать два свойства (CanShowWarning, IsVisible), но я не могу связать команду/действие кнопки с ShowWarning метод.

Я пробовал с deep property binding, и это снова работает для свойств, но не для действий.
Я также попытался с соединением cal:Model.Bind и cal:Message.Attach:

<Button cal:Model.Bind="{Binding UserNotFoundWarning}" 
     cal:Message.Attach="[Event Click] = [Action ShowWarning]" 
     IsEnabled="{Binding CanShowWarning}"> 
    ... 
    <DataTrigger Binding="{Binding IsVisible}" Value="True"> 
    ... 
</Button> 

Это похоже на работу во время выполнения, но кал: Model.Bind делает конструктор VS полностью непригодным для использования, управления пользовательского интерфейса не отображаются.

Я искал вокруг совсем немного, но я не мог найти фактическое решение, которое позволило бы мне также работать с дизайнером. Мне кажется странным, что я могу найти примеры глубокой привязки свойств, а не действий.

Любая идея, как решить эту проблему?

+0

Вам действительно нужно, чтобы UserNotFoundWarning являлся свойством 'interface'? Кажется странным иметь виртуальную машину для простой кнопки, простой способ. –

+0

Командная строка VM содержит несколько таких кнопок. Каждая кнопка подписывается на некоторый уровень бизнес-уровня, который можно наблюдать, чтобы определить, должно ли оно быть видимым. Таким образом, каждый из них должен иметь свою собственную IsXxxWarningButtonVisible с такой логикой, и VM командной строки потребует этих зависимостей бизнес-уровня, чтобы просто передать их этой логике. Затем все кнопки разделяют общую логику, связанную с командой и ее защитой, только между ними меняется только фактический текст предупреждения. Я начал этот путь, но потом у меня было много ShowXxxWarning, CanShowXxxWarning, IsXxxWarningVisible ... не так уж хорошо – superjos

+0

Помимо дизайнерских решений, которые, я уверен, могу пересмотреть и поговорить, есть способ - в общем - связать события/действия для вложенных методов VM? – superjos

ответ

4

Вот мой обходной путь:

private static void EnableNestedViewModelActionBinding() 
{ 
    var baseGetTargetMethod = ActionMessage.GetTargetMethod; 
    ActionMessage.GetTargetMethod = (message, target) => 
    { 
     var methodName = GetRealMethodName(message.MethodName, ref target); 
     if (methodName == null) 
      return null; 

     var fakeMessage = new ActionMessage { MethodName = methodName }; 
     foreach (var p in message.Parameters) 
      fakeMessage.Parameters.Add(p); 
     return baseGetTargetMethod(fakeMessage, target); 
    }; 

    var baseSetMethodBinding = ActionMessage.SetMethodBinding; 
    ActionMessage.SetMethodBinding = context => 
    { 
     baseSetMethodBinding(context); 
     var target = context.Target; 
     if (target != null) 
     { 
      GetRealMethodName(context.Message.MethodName, ref target); 
      context.Target = target; 
     } 
    }; 
} 

private static string GetRealMethodName(string methodName, ref object target) 
{ 
    var parts = methodName.Split('.'); 
    var model = target; 
    foreach (var propName in parts.Take(parts.Length - 1)) 
    { 
     if (model == null) 
      return null; 

     var prop = model.GetType().GetPropertyCaseInsensitive(propName); 
     if (prop == null || !prop.CanRead) 
      return null; 

     model = prop.GetValue(model); 
    } 
    target = model; 
    return parts.Last(); 
} 

Вызов EnableNestedViewModelActionBinding() один раз из вашего загрузчике, и это позволит вам связать действия с методами вложенной модели, используя обычную точечную нотацию. Например.

cal:Message.Attach="[Event Click] = [Action UserNotFoundWarning.ShowWarning]" 

Edit: обратите внимание, что это не будет работать, если вы измените вложенный экземпляр ViewModel во время выполнения. Например. если вы присвоите свой UserNotFoundWarning чему-то новому после того, как произошло связывание - Caliburn все равно вызовет действия в предыдущем экземпляре.

+0

Это выглядит очень хорошо ... мы попробуем его в ближайшие дни! – superjos

+0

Спасибо. Я просто попробовал ваше решение, и я читал об ограничениях.Мы назначаем вложенные ViewModels во время выполнения во время родительской конструирования, но потом они никогда не меняются в течение срока службы родителя, поэтому я думаю, что он должен работать – superjos

+0

, который хорошо работал, также с упрощенной нотой 'cal: Message.Attach =" UserNotFoundWarning.ShowWarning "' , Благодаря! – superjos