Laden...

WPF DataContext Zugriff wird verweigert

Erstellt von schirmi87 vor 8 Jahren Letzter Beitrag vor 8 Jahren 1.451 Views
S
schirmi87 Themenstarter:in
13 Beiträge seit 2010
vor 8 Jahren
WPF DataContext Zugriff wird verweigert

Hallo Zusammen,

ich habe parallel zu meiner MainWindow eine MainWindowViewModel Klasse erstellt, die mit allen Bedienoberflächen Elementen gebunden ist. Allerdings will ich einige Variablen davon in der Interaktionslogik (MainWindow.cs) der Oberfläche ändern können und die Änderung soll zum Beispiel in einer TextBox auch dargestellt werden. In meinem kleinen Beispiel habe ich einfach in meinem MainWindowViewModel einen String deklariert, der nach dem zuweisen der MainWindowViewModels Objekts mit

this.DataContext = mainWindowViewModel;

sich noch ändern lässt.

mainWindowViewModel.TextBoxString = "Test1";

allerdings wird in einer anderen Funktion außerhalb des Konstruktors der geänderte Wert nicht mehr in meiner TextBox dargestellt und erhalte dafür einen WPF DataContext Zugriff Error.

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            //an dieser Stelle wird der Zugriff auf die Textbox verweigert, wie kann ich das Problem am besten loesen?
            mainWindowViewModel.TextBoxString = "Test2";
        } 

Hat jemand vielleicht eine Idee, wie ich das besser machen kann?
Damit ich nicht 1000 Codezeilen poste habe ich mein Projekt auf ein Minimum beschränkt und versuche es einmal so klar wie möglich darzustellen.

Meine MainWindowViewModel


namespace WPF_Datacontext_Zugriff
{
    class MainWindowViewModel
    {
        private String _textBoxString;
        public String TextBoxString
        {
            get { return _textBoxString; }
            set
            {
                _textBoxString = value;
                NotifyPropertyChanged("TextBoxString");
            }
        }
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
        #region Private Helpers
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }
}

Die XML von meiner Bedienoberfläche


<Window x:Class="WPF_Datacontext_Zugriff.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Datacontext_Zugriff"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button x:Name="button1" Content="Change TextBoxString" HorizontalAlignment="Left" Margin="135,140,0,0" VerticalAlignment="Top" Width="140" Click="button1_Click"/>
        <TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="23" Margin="135,100,0,0" TextWrapping="Wrap" Text="{Binding TextBoxString, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Top" Width="140" />
    </Grid>
</Window>


namespace WPF_Datacontext_Zugriff
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MainWindowViewModel mainWindowViewModel;
        public MainWindow()
        {
            InitializeComponent();
            mainWindowViewModel = new MainWindowViewModel();
            this.DataContext = mainWindowViewModel;
            //an dieser Stelle kann ich meinem Textboxstring einen Wert zuweisen, der auch in die Textbox uebernommen wird
            mainWindowViewModel.TextBoxString = "Test1";

        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            //allerdings wird mir an dieser Stelle der Zugriff auf die Textbox in meiner Oberflaeche verweigert, wie kann ich das Problem am besten loesen? 
            mainWindowViewModel.TextBoxString = "Test2";
        }
    }
}

Vielen Dank schon mal
schirmi87

2.207 Beiträge seit 2011
vor 8 Jahren

Hallo schirmi87,

arbeite sauber nach MVVM mit Commands, dann brauchst du dein Click-Handler nicht. Dann noch NotifyPropertyChanged werfen und das Problem sollte gelöst sein.

Gruss

Coffeebean

F
10.010 Beiträge seit 2004
vor 8 Jahren

Das eigentliche Problem ist hier eher das du MVVM nur halb machst.

  1. Sobald man im Xaml ein "x:Name" stehen hat, ist zu 95% eine Verletzung des Patterns angedacht.
  2. Das selbe gilt wenn Click o.ä. Eventhandler benutzt werden.
  3. Und ebenso sobald Zuweisungen innerhalb des Codebehinds gemacht werden.

Also implementiere ICommand ( z.b. RelayCommand googln ) für das Button Command und mache Änderungen dann nur im VM.

3.003 Beiträge seit 2006
vor 8 Jahren

Ergänzend - an deiner Stelle würde ich auch das Zuweisen und Erzeugen des VM per XAML machen. Dort genau liegt auch die Ursache für den Fehler, den du hast.


<Window.Resources>
   <local:MainWindowViewModel />
</Window.Resources>

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

212 Beiträge seit 2008
vor 8 Jahren

OT:

Das eigentliche Problem ist hier eher das du MVVM nur halb machst.

  1. Das selbe gilt wenn Click o.ä. Eventhandler benutzt werden.

Also implementiere ICommand ( z.b. RelayCommand googln ) für das Button Command und mache Änderungen dann nur im VM.

Ich arbeite schon eine ganze Weile mit RelayCommands. Aber ich bin bis heute noch nicht wirklich davon überzeugt, ich mache das nur weil es angeblich "State of the art" ist. Ich finde der Code für die Commands bläht die VMs ziemlich auf und macht es auch nicht unbedingt Wartungsfreundlich.

Eigentlich gefällt mir so ein Konstrukt viel besser:


private void buttonRefresh_Click(object sender, RoutedEventArgs e)
{
      ((MyDataObjectViewModel)this.DataContext).Refresh();
}

So benötigt man exakt die seleben Kenntnisse vom ViewModel. Was spricht gegen das bischen Code Behind? Ich finde auch so liegt eine strikte Trennung vor, mit weniger Code, sowohl im XAML und ViewModel.

Was spricht dagegen? Ich überlege schon länger die Commands wieder zurück zu bauen....

Gruß
Christoph

3.170 Beiträge seit 2006
vor 8 Jahren

Hallo,

Was spricht dagegen?

z.B. dass so ein Command auch noch ein CanExecute zur Verfügung stellt, über das festgestellt wird ob das Kommando ausgeführt wird. Wenn Deine Buttons immer aktiv sind --> kein Problem.
Andernfalls fängst Du dann wieder an im CodeBehind irgendwelche Enabled/Disabled Dinger reinzufummeln - was ja im ViewModel nicht geht, da es das UI nicht kennt (kennen soll).
Dann fängst Du an aus dem CodeBehind auf Deine INotifyPropertyChanged zu subscriben etc.
Und das bläht den Code dann wirklich auf.

Oder Du hast eben für jeden Button nochmal Properties im ViewModel - dann sparst Du aber auch nix.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

2.207 Beiträge seit 2011
vor 8 Jahren

Hallo Christoph1972,

Ich finde der Code für die Commands bläht die VMs ziemlich auf und macht es auch nicht unbedingt Wartungsfreundlich.

deswegen kann man Commands auch in einer seperaten Klasse definieren und sie übers ViewModel anbieten. Die Funktionalität hat man dann gekapselt in der eigenen Command-Klasse mit allen Vorzügen (CanExecute, etc.) und man kann es gut testen.

Gruss

Coffeebean

5.657 Beiträge seit 2006
vor 8 Jahren

Hi Christoph1972,

zusätzlich zu dem, was meine Vorredner bereits gesagt haben:

Was spricht gegen das bischen Code Behind?

Du kannst deine Commands und ViewModels automatisiert testen, aber nicht die Funktionalität des Code-Behind.

Christian

Weeks of programming can save you hours of planning

3.170 Beiträge seit 2006
vor 8 Jahren

Hallo,

nun mal zurück zum eigentlichen Problem:
Mal abgesehen davon, dass das MainViewModel natürlich INotifyPropertyChanged auch implementieren sollte (in der Deklaration, nicht nur die Member reinpacken), läuft der Code aus dem Startbeitrag 1:1 so wie er ist bei mir ohne Fehler.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

F
10.010 Beiträge seit 2004
vor 8 Jahren

@Christoph1972:
MVVM sollte benutzt werden um die Logik unabhängig von der UI Testen zu können.

Ich habe aber vor kurzem hier die CommandMap gepostet ( ist auch etwas angepasst seit dem ) bei der ist es dann auch nur


<Button Content="Change TextBoxString" HorizontalAlignment="Left" Margin="135,140,0,0" VerticalAlignment="Top" Width="140" Command="{ Binding Commands.RefreshCommand}"/>


public void RefreshCommand()
{
}

oder ggf noch


public bool CanRefreshCommand()
{
    return ....;
}

Auch Caliburn.Micro und andere Biblioteken erlauben das so.

S
schirmi87 Themenstarter:in
13 Beiträge seit 2010
vor 8 Jahren

Hallo zusammen,

saucool und vielen lieben Dank. Mit euren Antworten habt ihr mich auf die richtige Fährte gebracht. Das Binding mit den Commands habe ich bisher immer vernachlässigt, da ich es als sehr aufwendig empfunden habe. Allerdings scheint es, dass es der richtige Weg ist, wenn man es vernünftig schreiben will. Als WPF Anfänger freut man sich immer über die schicken Click Events und schreibt dann einfach drauf los bis man dann erst sieht, dass es so nicht funktioniert.

Vielen Dank euch allen
schirmi87

212 Beiträge seit 2008
vor 8 Jahren

Vielen Dank soweit, besonders für den Tipp mit der CommandMap, das werde ich mir mal anschauen!

Gruß
Christoph