2012-04-13 16 views
7

Powoli uczę się WPF przy użyciu this article i innych zasobów.Dynamicznie generować proste widoki z ViewModels w WPF?

Skupiam się na logice aplikacji - definiując model + viewModel i tworząc polecenia, które na nich działają. Nie widziałem jeszcze widoku i formatu .xaml.

Podczas pracy nad logiką chcę mieć widok, który może wyrenderować dowolny model widoku, do którego go przypisam. Widok powinien

  • świadczą publiczne string właściwości jak pola tekstowe i powiązać pole tekstowe do właściwości
  • Render nazwę własności jako etykieta.
  • Render żadnej publicznej własności „komenda” jako jednego przycisku, a wiążą się przycisk do komendy (chyba tylko jeśli komenda nie wymaga żadnych argumentów?)

Czy coś jak to możliwe, podczas gdy utrzymywanie wzorca projektowego MVVM? Jeśli tak, jak ja to osiągnąć? Ponadto, artykuł sugeruje, aby unikać używania kodu .xaml codebehind - czy ten widok może być implementowany w czystym Xaml?

+0

myślę ów świetny pomysł! Możesz mieć działającą aplikację bez żadnego, ale najbardziej podstawowego interfejsu użytkownika i rozpocząć testowanie viewmodel/modelu, zanim projektant zacznie działać. =) – Jens

+0

Nie trzeba widoku, aby przetestować swój viewmodel. to, co mvvm dla;) – blindmeis

+0

Nie potrzebujesz interfejsu użytkownika do * jednostki * testowania viewmodel, ale myślę, że podstawowy UI byłby bardzo pomocny w sprawdzeniu, czy wszystkie składniki współpracują poprawnie. – Jens

Odpowiedz

6

Nie sądzę, że jest to możliwe tylko w XAML. Jeśli chcesz wygenerować swoje widoki w środowisku wykonawczym, musisz użyć refleksji nad swoimi modelami ViewModels i odpowiednio wygenerować sterowanie. Jeśli chcesz generować widoki podczas kompilacji, możesz wygenerować pliki Xaml z ViewModels w czasie kompilacji za pomocą jakiegoś silnika szablonów (takiego jak T4 lub szablon łańcucha) lub CodeDom. Lub możesz pójść dalej i mieć jakiś format metadanych (lub nawet DSL), z którego będziesz generował zarówno modele i widoki, i tak dalej. To zależy od potrzeb Twojej aplikacji.

A także w kodzie MVVM z tyłu jest Ok dla logiki wizualnej i wiązania z modelem/viewmodel, które nie mogą być wykonane tylko w XAML.

3

Nie jestem pewien, czy jest to odpowiednie zastosowanie dla podejścia "czystego MVVM", z pewnością nie wszystko zostanie osiągnięte po prostu przez wiązanie. A ja po prostu wyrzuciłbym ideę unikania używania kodu z tyłu dla twojego "widoku", jest to zadanie z natury programistyczne. Jedną rzeczą, na którą powinieneś się trzymać, jest to, że ViewModel nie ma wiedzy na temat widoku, więc gdy zastąpisz go "prawdziwą rzeczą", nie ma już żadnej pracy.

Ale z pewnością wydaje się rozsądna rzecz do zrobienia; brzmi to prawie jak wizualizator debugowania - możesz w tym celu wykorzystać istniejące narzędzie.

(Jeśli chcesz to zrobić głównie w XAML ze standardowymi ItemsControl s i szablony, możesz napisać konwerter, aby odsłonić właściwości swojego ViewModel przez odbicie w jakiejś formie, którą możesz powiązać, zbiór obiektów opakowania z odsłoniętymi metadane, ale myślę, że upewnienie się, że eksponowane właściwości są odpowiednio wiążące, byłoby więcej pracy niż jest warte)

1

Wskaźnik: Wygeneruj dynamiczny DataTemplate jako ciąg związany z konkretną maszyną wirtualną (targetem). Przetwórz go przez XamlReader. Umieść go w zasobach aplikacji w kodzie.

Po prostu pomysł .. uruchom z nim ... Powinien zostać wykonany przez inny typ niż View lub ViewModel.

3

W połowie realizacji tego teraz mam nadzieję, że poniższy kod pomoże każdemu, kto spróbuje to zrobić. Fajnie byłoby zamienić się w solidniejszą bibliotekę.

AbstractView.xaml:

<UserControl x:Class="MyApplication.View.AbstractView" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <StackPanel Name="container"> 
    </StackPanel> 
</UserControl> 

AbstractView.xaml.cs:

public partial class AbstractView : UserControl 
{ 
    public AbstractView() 
    { 
     InitializeComponent(); 

     DataContextChanged += Changed; 
    } 

    void Changed(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     object ob = e.NewValue; 
     var props = ob.GetType().GetProperties(); 

     List<UIElement> uies = new List<UIElement>(); 
     foreach (var prop in props) 
     { 
      if (prop.PropertyType == typeof(String)) 
       uies.Add(makeStringProperty(prop)); 
      else if (prop.PropertyType == typeof(int)) 
       uies.Add(makeIntProperty(prop)); 
      else if (prop.PropertyType == typeof(bool)) 
       uies.Add(makeBoolProperty(prop)); 
      else if (prop.PropertyType == typeof(ICommand)) 
       uies.Add(makeCommandProperty(prop)); 
      else 
      { 
      } 
     } 

     StackPanel st = new StackPanel(); 
     st.Orientation = Orientation.Horizontal; 
     st.HorizontalAlignment = HorizontalAlignment.Center; 
     st.Margin = new Thickness(0, 20, 0, 0); 
     foreach (var uie in uies) { 
      if (uie is Button) 
       st.Children.Add(uie); 
      else 
       container.Children.Add(uie); 
     } 
     if (st.Children.Count > 0) 
      container.Children.Add(st); 

    } 

    UIElement makeCommandProperty(PropertyInfo prop) 
    { 
     var btn = new Button(); 
     btn.Content = prop.Name; 

     var bn = new Binding(prop.Name); 
     btn.SetBinding(Button.CommandProperty, bn); 
     return btn; 
    } 

    UIElement makeBoolProperty(PropertyInfo prop) 
    { 
     CheckBox bx = new CheckBox(); 
     bx.SetBinding(CheckBox.IsCheckedProperty, getBinding(prop)); 
     if (!prop.CanWrite) 
      bx.IsEnabled = false; 
     return makeUniformGrid(bx, prop); 
    } 

    UIElement makeStringProperty(PropertyInfo prop) 
    { 
     TextBox bx = new TextBox(); 
     bx.SetBinding(TextBox.TextProperty, getBinding(prop)); 
     if (!prop.CanWrite) 
      bx.IsEnabled = false; 

     return makeUniformGrid(bx, prop); 
    } 

    UIElement makeIntProperty(PropertyInfo prop) 
    { 
     TextBlock bl = new TextBlock(); 
     bl.SetBinding(TextBlock.TextProperty, getBinding(prop)); 

     return makeUniformGrid(bl, prop); 
    } 

    UIElement makeUniformGrid(UIElement ctrl, PropertyInfo prop) 
    { 
     Label lb = new Label(); 
     lb.Content = prop.Name; 

     UniformGrid u = new UniformGrid(); 
     u.Rows = 1; 
     u.Columns = 2; 
     u.Children.Add(lb); 
     u.Children.Add(ctrl); 

     return u; 
    } 

    Binding getBinding(PropertyInfo prop) 
    { 
     var bn = new Binding(prop.Name); 
     if (prop.CanRead && prop.CanWrite) 
      bn.Mode = BindingMode.TwoWay; 
     else if (prop.CanRead) 
      bn.Mode = BindingMode.OneWay; 
     else if (prop.CanWrite) 
      bn.Mode = BindingMode.OneWayToSource; 
     return bn; 
    } 

} 
+0

Czy możesz podać link do końcowego projektu (jeśli był on dostępny w systemie otwartym)? Byłbym bardzo zainteresowany, aby zobaczyć, jak to wygląda i co udało się zrobić. – bachr

Powiązane problemy