wake-up-neo.net

Wpf-Datacontext-Bindung mit MVVM zwischen Ansichtsmodell und Ansicht

Ich habe gerade angefangen, MVVM zu lernen, und hier scheint eine grundlegende Frage zu sein, aber ich habe den ganzen Tag damit verbracht, es herauszufinden.

Ich habe eine Lösung, die 3 Projekte enthält, eines für Model, eines für ViewModel und eines für View. Das Modell enthält eine Klasse mit 2 Eigenschaften Text und CheckStatus.

Das ViewModel hat eine Liste namens listOfItems mit drei Elementen. Jedes Element verfügt über diese beiden Eigenschaften des Modells.

Die Ansicht enthält eine Listenansicht, in der sich eine CheckBox befindet. Was ist der richtige Weg, um den CheckBox-Inhalt an die Eigenschaft Text zu binden?

Hier ist das Modell

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace TheModel
{
public class CheckBoxListModel : INotifyPropertyChanged
{
    private string text;
    public string Text
    {
        get { return text; }
        set
        {
            text = value;
            RaiseChanged("Text");
        }
    }

    private bool checkStatus;
    public bool CheckStatus
    {
        get { return checkStatus; }
        set
        {
            checkStatus = value;
            RaiseChanged("CheckStatus");
        }
    }

    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
   }
}

Hier ist das Ansichtsmodell

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using TheModel;

namespace TheViewModel
{
public class TheViewModel
{
    public List<CheckBoxListModel> ListOfItems { get; set; }

    public TheViewModelClass()
    {
        ListOfItems = new List<CheckBoxListModel>
        {
        new CheckBoxListModel
        {
            CheckStatus = false,
            Text = "Item 1",
        },
        new CheckBoxListModel
        {
            CheckStatus = false,
            Text = "Item 2",
        },
        new CheckBoxListModel
        {
            CheckStatus = false,
            Text = "Item 3",
        }
    };
    }

    public static implicit operator List<object>(TheViewModelClass v)
    {
        throw new NotImplementedException();
    }
   }
}

und hier ist der View XAML

 <UserControl
 xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
 xmlns:ctrl="clr-namespace:TheView.Managers" xmlns:TheViewModel="clr-
 namespace:TheViewModel;Assembly=TheViewModel" 
 x:Class="TheView.Styles.ListViewDatabaseStyle">

<UserControl.DataContext>
    <TheViewModel:TheViewModelClass/>
</UserControl.DataContext>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="100"/>
    </Grid.RowDefinitions>
    <Button Content="Continue" Style="{StaticResource ButtonStyle}" 
          Margin="1104,27,40,40"/>
    <ListView x:Name="listView1" SelectionMode="Multiple" 
              Style="{StaticResource ListViewStyle}" Margin="10,55,10,10"
              ctrl:ListViewLayoutManager.Enabled="true" ItemsSource="
          {Binding TheViewModelClass}" >
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Competency Items" 
                  ctrl:ProportionalColumn.Width="1100"/>
            </GridView>
        </ListView.View>
        <ListView.ItemContainerStyle >
            <Style TargetType="{x:Type ListViewItem}">
                <Setter Property="IsSelected" Value="{Binding 
                             CheckedStatus}"/>
                <Setter Property="HorizontalContentAlignment" 
                              Value="Stretch"/>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
            <DataTemplate>
                <CheckBox  
                     Click="CheckBox_Click"
                     Content="{Binding Path=TheViewModelClass.Text}"
                     IsChecked="{Binding 
                     Path=TheViewModelClass.CheckedStatus}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>
</UserControl>

Hier ist der Code hinter dem View. Ich weiß, ich sollte hier nicht etwas haben, aber wohin sollte dieser Teil gehen?

using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System;
using System.Text;
using TheViewModel;

namespace TheView.Styles
{
public partial class ListViewDatabaseStyle : UserControl
{
    public ListViewDatabaseStyle()
    {
        InitializeComponent();
    }

    public List<string> selectedNames = new List<string>();
    private void CheckBox_Click(object sender, RoutedEventArgs e)
    {
        var ChkBox = sender as CheckBox;
        var item = ChkBox.Content;
        bool isChecked = ChkBox.IsChecked.HasValue ? ChkBox.IsChecked.Value 
         : false;
        if (isChecked)
            selectedNames.Add(item.ToString());
        else
            selectedNames.Remove(item.ToString());
    }
  }
 }
8
user7900863

Zuerst. Abhängigkeiten von Projekten festlegen. ViewModel muss Zugriff auf das Modell haben. (View- und Model-Projekte müssen sich nicht auf andere Projekte beziehen.) Wenn ich Sie wäre, würde ich ein Startup-Projekt erstellen, um das Steuerelement auf ViewModel zu übertragen. Dieses "StartUp" -Projekt sollte WPF sein, alle anderen sollten "." Klassenbibliothek ", aber vergessen Sie nicht, die erforderlichen Referenzen zu Projekten hinzuzufügen (Zum Beispiel die system.xaml für Ihr Ansichtsprojekt, um Benutzersteuerelemente zu erstellen.)

Abhängigkeiten von Projekten: - StartUp -> ViewModel; (- ViewModel -> View; oder vermeiden Sie dies mit DI) - ViewModel -> Model; (Ich sollte ein anderes Projekt für Schnittstellen erstellen, nur dies ist nur meine Perversionen.)

StartUp Project: Nun sollte in Ihrem Startprojekt (WPF) in (app.xaml.cs) enthalten sein:

protected override void OnStartup(StartupEventArgs e)
{
    // delete the startupuri tag from your app.xaml
    base.OnStartup(e);
    //this MainViewModel from your ViewModel project
    MainWindow = new MainWindow(new MainViewModel());
} 

Das einzige (Window) in Ihrem Startup-Wpf-Projekt (zur Anzeige Ihrer UserControls).

MainWindow.xaml-Inhalt:

<Window x:Class="StartUp.MainWindow"
            xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
            Title="MainWindow" WindowState="Maximized" WindowStyle="None" AllowsTransparency="True">
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Control}"/>
</Window>

(und xaml.cs)

  public partial class MainWindow : Window
    {
        public MainWindow(INotifyPropertyChanged ViewModel)
        {
            InitializeComponent();
            this.DataContext = ViewModel;
            this.Show();
        }
    }

Und das ist Ihr ganzes StartUp WPF-Projekt ... Auf diese Weise haben wir Ihrem ViewModel-Projekt die Kontrolle überlassen.

(Okay, es ist nur ein Extra, aber ich sollte einen "ViewService" für meine UserControls erstellen)

Benutzeroberfläche zum Finden aller Ansichten und Anpassen der Ansicht mit ViewModel.

public interface IControlView
{
    INotifyPropertyChanged ViewModel { get; set; }
}

Ich habe ein Singleton erstellt, um meine Ansichten mit meinen Ansichtsmodellen zu speichern und abzugleichen. (Sie können diesen Teil überspringen.) Ich habe dies im Projekt Model definiert.

 public class ViewService<T> where T : IControlView
    {
        private readonly List<WeakReference> cache;

        public delegate void ShowDelegate(T ResultView);
        public event ShowDelegate Show;
        public void ShowControl<Z>(INotifyPropertyChanged ViewModel)
        {
            if (Show != null)
                Show(GetView<Z>(ViewModel));
        }

        #region Singleton

        private static ViewService<T> instance;
        public static ViewService<T> GetContainer
        {
            get
            {
                if (instance == null)
                {
                    instance = new ViewService<T>();
                }
                return instance;
            }
        }

        private ViewService()
        {
            cache = new List<WeakReference>();
            var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where(r => typeof(T).IsAssignableFrom(r) && !r.IsInterface && !r.IsAbstract && !r.IsEnum);

            foreach (Type type in types)
            {
                cache.Add(new WeakReference((T)Activator.CreateInstance(type)));
            }
        }

        #endregion

        private T GetView<Z>(INotifyPropertyChanged ViewModel)
        {
            T target = default(T);
            foreach (var wRef in cache)
            {
                if (wRef.IsAlive && wRef.Target.GetType().IsEquivalentTo(typeof(Z)))
                {
                    target = (T)wRef.Target;
                    break;
                }
            }

            if(target==null)
                target = (T)Activator.CreateInstance(typeof(Z));

            if(ViewModel != null)
                target.ViewModel = ViewModel;

            return target;
        }

    }

Und jetzt haben Sie einen "Dienst", um Ihre UserControls im Hauptfenster aus Ihrem ViewModel anzuzeigen:

public class MainViewModel : INotifyPropertyChanged
    {

        private IControlView _control;
        public IControlView Control
        {
            get
            {
                return _control;
            }
            set
            {
                _control = value;
                OnPropertyChanged();
            }
        }

        public MainViewModel()
        {   //Subscribe for the ViewService event:   
            ViewService<IControlView>.GetContainer.Show += ShowControl;
            // in this way, here is how to set a user control to the window.
            ViewService<IControlView>.GetContainer.ShowControl<ListViewDatabaseStyle>(new TheViewModel(yourDependencyItems));
           //you can call this anywhere in your viewmodel project. For example inside a command too.
        }

        public void ShowControl(IControlView ControlView)
        {
            Control = ControlView;
        }

        //implement INotifyPropertyChanged...
        protected void OnPropertyChanged([CallerMemberName] string name = "propertyName")
        {
           PropertyChangedEventHandler handler = PropertyChanged;
           if (handler != null)
           {
               handler(this, new PropertyChangedEventArgs(name));
           }
        }

           public event PropertyChangedEventHandler PropertyChanged;
    }

Wenn Sie diesen "ViewService" nicht verwenden möchten. Erstellen Sie einfach eine UserControl-Instanz, ordnen Sie DataContext of View Ihrem ViewModel zu und übergeben Sie diese Ansicht der Control-Eigenschaft . Hier finden Sie Ihre ViewModel-Liste (noch im ViewMoldel-Projekt).

public class TheViewModel
    {
        private readonly ObservableCollection<ISelectable> listOfItems;
        public ObservableCollection<ISelectable> ListOfItems 
        {
            get { return listOfItems; }
        }

        public ICommand SaveCheckedItemsText{
            get{ return new RelayCommand(CollectNamesOfSelectedElements);}
        }

        public IEnumerable<ISelectable> GetSelectedElements
        {
            get { return listOfItems.Where(item=>item.CheckStatus); }
        }

        public TheViewModel(IList<ISelectable> dependencyItems)
        {
            listOfItems= new ObservableCollection<ISelectable>(dependencyItems);
        }

        //here is your list...
        private List<string> selectedNames

        //use this...
        private void CollectNamesOfSelectedElements()
        {
           selectedNames = new List<string>();
           foreach(ISelectable item in GetSelectedElements)
           {
             //you should override the ToString in your model if you want to do this...
             selectedNames.Add(item.ToString());
           }
        }

    }

RelayCommand Artikel

View: (Behalten Sie alle Ihre Benutzersteuerelemente bei.)

In Ihrem UserControl (xaml):

<UserControl x:Class="View.ListViewDataStyle"
             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" namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
             mc:Ignorable="d">
<Button Command={Binding SaveCheckedItemsText}/>
<!-- Another content -->
    <ListView ItemsSource="{Binding ListOfItems}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</UserControl>

Und mit Schnittstelle ist hier der Code xaml.cs (für UserControls):

public partial class ListViewDatabaseStyle : UserControl, IControlView
    {
        public ListViewDatabaseStyle ()
        {
            InitializeComponent();
        }

        public INotifyPropertyChanged ViewModel
        {
            get
            {
                return (INotifyPropertyChanged)DataContext;
            }
            set
            {
                DataContext = value;
            }
        }
    }

Und das letzte ist das Model Projekt mit Ihren Modellen:

 public interface ISelectable
    {
        bool CheckStatus { get; set; }
    }

public class CheckBoxListModel : INotifyPropertyChanged, ISelectable
{
    private string text;
    public string Text
    {
        get { return text; }
        set
        {
            text = value;
            RaiseChanged("Text");
        }
    }

    private bool checkStatus;
    public bool CheckStatus
    {
        get { return checkStatus; }
        set
        {
            checkStatus = value;
            RaiseChanged("CheckStatus");
        }
    }

    private void RaiseChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
   }
}

Entschuldigen Sie mich für englische Grammatikfehler. Ich hoffe, Sie haben meinen Beitrag verstanden.

Update: Verwenden Sie die DI-Technik. um den Verweis auf die Ansicht vom Ansichtsmodell zu vermeiden. DI-Dienst wird das richtige Objekt mit der Konstruktorinjektion injizieren.

3
Péter Hidvégi
<UserControl.DataContext>
    <TheViewModel:TheViewModelClass/>
</UserControl.DataContext>

<ListView ItemsSource="{Binding ListOfItems}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
1
Liero