[MVVM] Canvas Background Image via Slider ändern?

1 Antwort

Vom Fragesteller als hilfreich ausgezeichnet

Tipp 1: Da du ja bei XAML noch am Anfang stehst, würde ich dir empfehlen, den XAML-Code auch selbst zu schreiben, auch wenn der Designer in Visual Studio es viel einfacher machen mag. Gründe, die dafür sprechen:

  • Du musst dich mehr mit XAML beschäftigen
  • Der XAML-Code enthält nur das, was er wirklich braucht

Für deine grundsätzliche Oberflächenaufteilung würde ich dir daher empfehlen, ein Grid zu verwenden. Dieses lässt sich in beliebig viele Zeilen und Spalten aufteilen, die jeweils individuell gewichtet werden können. Dadurch wird deine Oberfläche flexibler, gerade wenn es darum geht, unterschiedliche Fenstergrößen nutzen zu können (aufgrund von Bildschirmgröße / weil der Nutzer die Fenstergröße ändert).

Ein einfaches Beispiel:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="0.9*" />
    <RowDefinition Height="0.1*" />
  </Grid.RowDefinitions>
  <ItemsControl Name="Sketch">
    <!-- ... -->
  </ItemsControl>
  <Slider Grid.Row="1" Margin="5" Maximum="15" Minimum="0" />
</Grid>

Dies würde dafür sorgen, dass das Fenster in zwei Zeilen (90% - 10%) aufgeteilt wird. Das erste Element in so einem Grid liegt automatisch in Zeile und Spalte 0. Weitere werden ebenso dort hineingelegt, wenn man die Attribute (Grid.Column, Grid.Row) nicht setzt.

In deinem ChangeValueEvent machst du einige seltsame Sachen. Collections wird wohl eine Klasse von dir sein. Sie ist allerdings schlecht benannt, denn es ist nicht klar, was sie nun wirklich macht. Mit der LoadFieldMap-Methode kann ich nicht viel anfangen, außer, dass da wohl BitmapImage-Objekte zurückkommen. Da du an der Stelle den Wert von images überschreibst, ist die Definition der Variable in Zeile 2 unnötig. Auch die vorletzte Zeile wird nicht gebraucht.

Nun aber zu dem eigentlichen Fehler: Du legst eine neue Instanz deines ViewModels (Mittelebene) an. Bestimmt wird da nun ein Image auf BitIm gesetzt und vielleicht hast du da auch ein PropertyChanged-Listener drauf, der nun reagiert. Aber die ViewModel-Instanz ist an kein View gebunden. Die wirkliche Instanz, die das View verwendet, wird dynamisch erstellt, ist vor dir also verborgen.

Wenn du ein Bild quick und dirty in der View-Klasse (MainWindow) setzen möchtest, dann solltest du eigentlich eher das Element selbst ansprechen. Da es sich um ein gekapseltes Element handelt (es wird ja dynamisch für das ItemsControl generiert), müsstest du den XAML-Tree parsen.

Statte dein Canvas erst einmal mit einem Namen aus:

<Canvas Name="MyCanvas"/>

Nach diesem Attribut wird folgend gesucht. Dafür benötigst du eine Parser-Methode. Du könntest dir an dieser Stelle eine Art statische Utils-Klasse anlegen, in der Extension methods definiert werden:

public static class TreeHelper
{
  public IEnumerable<T> FindVisualChildren<T>(this DependencyObject root) where T : DependencyObject
  {
    if (root != null)
    {
      for (var i = 0; i < VisualTreeHelper.GetChildrenCount(root); ++i)
      {
        DependencyObject child = VisualTreeHelper.GetChild(root, i);
        
        if (child != null && child is T)
        {
          yield return (T)child;
        }

        foreach (T childOfChild in FindVisualChildren<T>(child))
        {
          yield return childOfChild;
        }
      }
    }
  }
}

Diese Methode sucht nun zumindest erst einmal nach allen sichtbaren Kindern eines bestimmten Elements im Baum. Noch besser, ohne Zweifel, wäre eine Methode FindVisualChildByName, doch das überlasse ich an dieser Stelle einmal dir.

Und im Einsatz (in der Event-Handler-Methode):

var itemsPanelTemplate = Sketch.ItemsPanel.Template;
var myCanvas = FindVisualChildren<Canvas>(Sketch).FirstOrDefault(canvas => canvas.Name == "MyCanvas");

if (myCanvas != null)
{
  myCanvas.Background = new ImageBrush
  {
    ImageSource = new BitmapImage(new Uri("path/to/some/image"))
  };
}

Das alles ist, wie du siehst, ziemlich kompliziert und fühlt sich irgendwie auch nicht stabil an. Zudem ist es eine kleine Ressourcenschleuder.

Der bessere Weg wäre also einmal wieder über Bindings und MVVM.


Nevron 
Fragesteller
 28.03.2019, 01:02

Nabend,

offenbar ist der falsche Eindruck entstanden. Bindings und MVVM ist mein vorrangiges Anliegen. Ansonsten wäre das Programm womöglich schon fertig :)

Da ich am lernen bin und ich einige Sachen vorher noch nie gemacht habe, muss ich erstmal die grundlegenden Funktionen kennenlernen. Wenn ich dann erstmal weiß wie etwas geht, setzte ich das Ganze MVVM Komform um.

... deinem ChangeValueEvent machst du einige seltsame Sachen.

Das war wie geschrieben, nur etwas schnelles dahin gepflatschtes um Dinge zu testen. Ich mache es wie du sagst. Ich teile das Problem in kleinere Teile. Und da ich jedes Teilgebiet prüfe, beschaffe ich mir auf dem schnellen Weg die Daten. Auch wenn der Code manchmal aussieht wie Kraut und Ruben, ist das meistens nur die Kopie vom Programm wo als TestAreal dient. Ich würde das gerne öfters erwähnen, aber er meckert immer, dass ich nicht mehr als 3000 Zeichen schreiben darf, daher musste ich den Text etwas kürzen :)

Ich werde mich aber bemühen, in Zukunft meinen Code vorher aufzubereiten, damit kein falsches Bild entsteht.

Ich hab mir von RheinWerk das "Umfassende Handbuch" zu WPF geholt. Ich weiß, die Vorgänger Version gibts auch kostenlos im Netz. Hab aber ganz gerne auch was in der Hand. Das Problem konnte ich zwar bis jetzt nicht lösen, hab aber auf dem Weg trotzdem viel gelernt .

Der Weg ist das Ziel *g*

0
Nevron 
Fragesteller
 28.03.2019, 16:56
@Nevron

Ich entschuldige mich im Vorfeld bei dir. Ich bilde mir ein einen kleinen Fehler gefunden zu haben. Ich würde es normal nicht schreiben, aber für die, die nach mir das lesen.

Du schriebst:

  </ItemsControl>
  <Slider Grid.Row="1" Margin="5" Maximum="15" Minimum="0" />
</Grid>

Wenn man es so schreibt passiert nichts, wenn man die Grid.Row ändern möchte. Vermutlich weil der Slider dem ItemsControl untergeordnet ist, das ItemsControl aber die Stelle beibehält.

Ich konnte es leicht ändern, indem ich das Grid.Row = "1", dem ItemControl hinzugefügt habe. Dann hats geklappt.

Aber evtl hattest du auch etwas anderes gemeint und ich habe es nicht verstanden.

0
regex9  28.03.2019, 17:11
@Nevron

Überprüfe nochmals dein XAML. Der Slider ist dem ItemsControl nicht untergeordnet. Aber beide Elemente liegen in einem Grid. Wenn die Reihe für keines der beiden Elemente definiert wird, liegen sie übereinander in Reihe 1.

1
Nevron 
Fragesteller
 28.03.2019, 17:30
@regex9

Ja, der Slider war dem ItemsControl untergeordnet.

0
Nevron 
Fragesteller
 28.03.2019, 19:50
@Nevron

Also um es mal ganz für blöde zu reduzieren, muss ich das was die ObservableCollection von alleine macht(selbstständig updaten), selbst, mittels parsens des XAML Trees ( Was auch immer das ist ) machen? Und das ist der Grund, warum keine Bilder angezeigt werden?

0
regex9  28.03.2019, 20:04
@Nevron

Der eigentliche Grund, wieso dein Code dort oben nicht funktioniert, ist der, dass du die Instanz des ViewModels nicht kennst, die an das View gebunden ist (denn das regelt WPF intern). Wenn du sie kennen würdest, könntest du ihr Property BitIm setzen und ein PropertyChanged-Event könnte (sofern implementiert) ausgelöst werden.

Im View (du befindest dich ja in der Klasse MainWindow) kannst du daher höchstens auf den XAML-Tree zugreifen und dort Elemente verändern. Wenn du so Bilder setzen wollen würdest, bräuchtest du wiederum eine Verbindung zu einem ViewModel / Model, sonst kennst du ja die Bildpfade nicht.

1
Nevron 
Fragesteller
 28.03.2019, 21:50
@regex9

Ich wollte gerade eben schon was posten....Er wollte mir nichts anzeigen lassen. Hab sogar ein Button erstellt um die Backgroundfarbe einfach zu ändern. Nix.

Wieder was gelernt, obwohl ich erwartet hätte, dass das geht.

Mein Canvas (das jetzt auch einen Namen hat) war im ItemsControl.

Obwohl ich im MainWindow direkt das Control via Namen angesprochen

    SolidColorBrush brush = new SolidColorBrush(Colors.Blue);
      myCanvas.Background = brush;

habe und einen Wert zugewiesen habe, waren keinerlei Änderungen sichtbar.

Erst als ich das Canvas aus dem itemsControl raus habe, hat alles funktioniert. Sogar mein Event funktioniert plötzlich.

Ich dachte, in das itemsControl kann man alles mögliche wie in einen Grid reinmachen. Quasi ein Gerüst das man nach persönlichen Gusto formen kann und nicht in eine Richtung limitiert ist.

Warum wird ein Befehl an das Canvas vom ItemsControl blockiert?

Ich will gar nicht wissen, wieviel Stunden nur aufgrund dessen ins Nirvana gingen :)

Jetzt kann es endlich wieder weiter gehen.

0
regex9  28.03.2019, 22:07
@Nevron

Das Canvas gehört auch in das ItemsControl.

Begründet ist das Verhalten dadurch, das das Canvas im Template (das ist auch gewollt!) - also als Vorlage für das ItemsControl definiert wird. Wenn dein XAML-Code umgewandelt / ausgeführt wird, wird ein ItemsControl-Objekt erstellt, welches sich in seinem Aufbau an dem Template orientiert.

1
Nevron 
Fragesteller
 29.03.2019, 12:42
@regex9

Auch auf die Gefahr hin mich zum Affen zu machen:

Ich habe wieder einen Fehler gefunden, den ich gemacht habe. Ich weiß aber nicht so wirklich, warum das so ist. Ich habe Ahnung.

Der Teufel steckt sehr oft im Detail. Gerade hier, scheint ein Nest zu sein :D

Drücke ich einen Button und schreibe so:

  <ItemsControl Name="myControl" Grid.Row="0" ItemsSource="{Binding}">
      <Canvas>
          <Canvas.Background>
            <ImageBrush ImageSource="{Binding MyPic}"/>
          </Canvas.Background>
        </Canvas>
      </ItemsControl>

So wird mir nichts angezeigt. ( Die Bild Formate habe ich berücksichtigt)


    <ItemsControl Name="myControl" Grid.Row="0" ItemsSource="{Binding}">
        <Canvas Name="myCanvas">
          <Image Name="myBild" Source="{Binding MyPic}"/>           
        </Canvas>      
      </ItemsControl>

So wird es mir angezeigt und es läuft alles.

Die Background Variante wäre mir lieber. Bei der unteren Variante, wird das Image nicht auf Canvas Größe minimiert, sondern über den ganzen Bildschirm gestretched.

0
Nevron 
Fragesteller
 29.03.2019, 14:16
@Nevron

Ok, background ist offenbar nur für GradientBrush gedacht. Also Farben und keine Bilder.

0
Nevron 
Fragesteller
 29.03.2019, 14:20
@Nevron

aha!


<Image Name="myBild" Source="{Binding MyPic}" Width="{Binding Path=ActualWidth, ElementName=myCanvas}"/>
0
regex9  29.03.2019, 15:45
@Nevron

Wieso? Bereits in deiner Fragestellung hast du mit dem ImageBrush gearbeitet, der durchaus auch gut funktioniert.

1
Nevron 
Fragesteller
 29.03.2019, 16:16
@regex9

Ehrlich gesagt, bin ich da etwas durcheinander gekommen. Ständig meckert er, dass irgendwelche Verweise sich überschneiden.

System.Windows.Media.Drawing.ImageBrush und System.Drawing.ImageBrush. Solche Probleme gabs in WindowsForms nicht. Es ist verwirrend. Aber ich habs jetzt unter Kontrolle.

0
Nevron 
Fragesteller
 29.03.2019, 16:42
@Nevron

Und abgesehen davon merke ich, dass mir die Lust langsam vergeht. Ich hab mein ganzen Urlaub, ich will jetzt nicht sagen verschwendet, aber irgendwie dreh ich mich im Kreis. Habe ich ein Problem gelöst, tauchen 5 andere auf.

Ich kann dir einwas sagen.. ImageBrush, ist der größte Scheisdreck auf dieser Welt...

Mir qualmt der Kopf.

Warum wird mir dieses Gottverdammte Drecks Bild nicht angezeigt. Ich hab es wieder zum ImageBrush gemacht :D

Und seit das Canvas im ItemsControl drinnen ist, funktioniert das mit dem ImageBrush nicht mehr........sondern nur mit BItmapImages....

Evlt mach ich auch was falsch, oder es fehlt irgendwas...

Ich hatte vorher auch mal versucht mittels eine Collection mal als Itemsource einzubinden. Das geht auch nicht. Da meckert er , dass die Collection leer sein muss. Da hatte ich gegoogelt und herausgefunden, dass das so ist, weil es sich in einem Grid befindet.

Wussssaaaaaaaaaaa.

   <Grid Name="myGrid" Margin="209,659,965,60.8" Background="#FF222222">
      <Grid.RowDefinitions>
        <RowDefinition Height="0.9*"/>
        <RowDefinition Height="0.1*" />
      </Grid.RowDefinitions>
      <ItemsControl Name="myControl" Grid.Row="0">
        <Canvas Name="myCanvas">
          <Canvas.Background>
            <ImageBrush ImageSource="{Binding ImageBrushPic, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
          </Canvas.Background>
        </Canvas>
      </ItemsControl>

[URL=https://www.bilder-upload.eu/bild-af470f-1553873877.png.html]

[URL=https://www.bilder-upload.eu/bild-07945f-1553874014.png.html]

0
regex9  29.03.2019, 17:40
@Nevron

Der System.Windows-Namespace beinhaltet WPF-Klassen, wird daher in der PresentationFramework.dll definiert. Klassen unter System.Drawing stellen grundlegende Grafikfunktionalitäten zur Verfügung. Eine Klasse System.Drawing.ImageBrush gibt es meines Wissens nach nicht.

1
Nevron 
Fragesteller
 29.03.2019, 21:44
@regex9

Um auf die "Ich schiebe Slider, Bild ändert sich im Canvas" Sache zurückzukommen.

Ich hab mir das so vorgestellt:

  public void sliderChangeValue(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
      int zahl = (int)((sender as Slider).Value = Math.Round(e.NewValue, 0));

      Image =  CollectionAusBildern[zahl].Bild;

    }

Das ChangeValue-Event vom Slider nutzt als Value eine double Zahl.

Ich typecaste die Zahl in Int und nutze das Tickplacement des Sliders als Index für die eine x-beliebge Collection aus Bildern.

Schiebe ich den Regler z.b. auf Value 1, wird der Index 1 in der Collection angesprochen, die ensprechende Property mit dem Bild gefüllt, die als Binding an das View gebunden ist.

Ist diese vorgehensweise ratsam?

0
Nevron 
Fragesteller
 29.03.2019, 23:47
@Nevron

Ich hab mir jetzt mal ein Online Kurs gekauft für WPF. Hab mir da schon paar Kapitel reingezogen und schon einige Unklarheiten beseitigt. Dann hab ich die Basics inne und dann kann es in die Tiefe gehen.

Dann muss ich dich nicht ständig mit irgendwas nerven :P

0
Nevron 
Fragesteller
 30.03.2019, 01:20
@Nevron

Der Kurs ist sehr gut. Das puzzle setzt sich immer weiter zusammen. Jetzt versteh wie du manches gemeint hast.

Hab deine Antwortposts jetzt ausgedruckt um nachzuschlagen.

Das mit den ganzen grids ect sah nur am Anfang so chaotisch aus. Hab dein Ratschlag zu Herzen genommen und alles per Hand gemacht. Auf dem Weg hab ich viele Sachen entdeckt.

Hab den Code jetzt auch etwas aufgefrischt. Vernünftige Bezeichnungen ect. Ab einem gewissen Punkt kommt man sonst ins straucheln. Und wenn man in 2 Wochen in den Code wieder reinschaut, blickt man überhaupt nicht mehr durch.

0
regex9  30.03.2019, 01:20
@Nevron

Diese Vorgehensweise wäre richtig, lediglich mit double brauchst du gar nicht zu arbeiten. Setze für den Slider die Attribute TickFrequency und IsSnapToTickEnabled, um zu verhindern, dass Nutzer einen Wert zwischen 1 und 2 wählen können.

Ich bin nun einmal dazu gekommen, ein Beispiel zu schreiben, welches Bilder und Türen via Slider lädt. Ich stelle es in den folgenden Kommentaren vor.

1
regex9  30.03.2019, 01:43
@Nevron

Beachte, dass ich mal wieder für alle Klassen stets denselben Namespace verwendet habe, sodass für das XAML keine neuen Präfixe nötig sind. Das ist eine exemplarische Vorgehensweise. Bei deinem Projekt solltest du besser in verschiedene Namespaces aufteilen, gerade um Ordnung zu halten.

Door-Klasse:

namespace DoorkKeyManager
{
  public class Door
  {
    public Door(string name, int x, int y)
    {
      Name = name;
      X = x;
      Y = y;
    }
  
    public string Name { get; set; }
  
    public int X { get; set; }

    public int Y { get; set; }
  }
}

Hier habe ich höchstens die Property-Namen geändert. Ansonsten ist die Klasse bekannt.

Map-Klasse:

So wie ich es bisher mitbekommen habe, gibt es in deinem Programm verschiedene Karten / Pläne, auf denen die Türen angezeigt werden sollen. Das bedeutet, eigentlich benötigst du eine Kollektion an Karten, in denen die Türen gespeichert werden, um die Assoziation zwischen Karte und Tür zu haben.

using System.Collections.ObjectModel;

namespace DoorkKeyManager
{
  public class Map
  {
    public ObservableCollection<Door> Doors { get; set; }
  
    public string ImageUrl { get; set; }
  }
}

Für das Bild speichere ich nur einen Pfad / eine URL, wie du siehst. Das Model soll möglichst nur aus elementaren Typen (String, int, bool, ...) bestehen oder Typen, die ins Model passen. GUI-bezogenen Klassen (BitmapImage, ImageBrush, ...) sollen bekanntermaßen nicht einbezogen werden. Aber das View fordert ein BitmapImage. Daher wäre hier ein Converter das Mittel der Wahl. Dies ist einmal wieder ein WPF-spezifisches Feature. Mit dem Converter soll es dem View möglich sein, den Wert des Models selbst umwandeln zu können.

using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace DoorkKeyManager
{
  public class ImageUrlToImageConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo cultureInfo)
    {
      if (value is string)
      {
        return new BitmapImage(new Uri((string)value));
      }

      throw new InvalidOperationException("The value must be a string");
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo cultureInfo)
    {
      throw new NotImplementedException();
    }
  }                                                                                       }

Wichtig ist nur der Inhalt der ersten Methode. In dieser wird der gebundene Wert (der Bildpfad) zu einem String umgewandelt und dann ein Bild erstellt. Man könnte bei einem TwoWay-Binding auch dafür sorgen, dass die Eingabe aus dem View wieder zurück konvertiert wird. Das nutzen wir an dieser Stelle aber gar nicht. Von daher kann die Methode irgendetwas zurückgeben, was auch nur entfernt an ein object erinnert. Der Wert null wäre da bevorzugt (wenn es knallt, findet man den Fehler schnell) oder wie bei mir, der Wurf einer NotImplementedException.

1
regex9  30.03.2019, 01:56
@Nevron

Es folgt das ViewModel:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace DoorKeyManager
{
  public class DoorViewModel : INotifyPropertyChanged
  {
    private Map _currentMap;

    private int _currentSliderIndex;

    private readonly IList<Map> _maps;

    public DoorViewModel()
    {
      AddDoorCommand = new RelayCommand(param => AddDoor(), param => true);

      _maps = LoadMaps();
      CurrentSliderIndex = 0;
    }

    public ICommand AddDoorCommand { get; set; }

    public int CurrentSliderIndex
    {
      get => _currentSliderIndex;

      set
      {
        _currentSliderIndex = value;
        NotifyPropertyChanged(nameof(CurrentSliderIndex));

        CurrentMap = _maps[_currentSliderIndex];
      }
    }

    public Map CurrentMap
    {
      get => _currentMap;

      set
      {
        if (value != _currentMap)
        {
          _currentMap = value;
          NotifyPropertyChanged(nameof(CurrentMap));
        }
      }
    }

    public int NumberOfMaps => _maps.Count - 1;

    public event PropertyChangedEventHandler PropertyChanged;

    private void AddDoor()
    {
      _currentMap.Doors.Add(new Door("Test", 100, 50));
    }

    private IList<Map> LoadMaps()
    {
      return new List<Map>
      {
        new Map
        {
          ImageUrl = "some image url",
          Doors = new ObservableCollection<Door>
          {
            new Door("Map 1, Door 1", 250, 10),
            new Door("Map 1, Door 2", 100, 200),
            new Door("Map 1, Door 3", 370, 70)
          }
        },
        new Map
        {
          ImageUrl = "some image url",
          Doors = new ObservableCollection<Door>
          {
            new Door("Map 2, Door 1", 80, 30),
            new Door("Map 2, Door 2", 20, 110)
          }
        },
        new Map
        {
          ImageUrl = "some image url",
          Doors = new ObservableCollection<Door>()
        }
      };
    }

    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

Es hält Referenzen auf alle verfügbaren Maps - die Methode LoadMaps dient nur dem Beispiel, es werden 3 Maps geladen. Für die Image URLs habe ich bspw. diese verwendet:

Des Weiteren wird die aktuelle Map in einem Property gespeichert. Die Klasse implementiert das INotifyPropertyChanged-Interface, sodass das View über Änderung dieses Properties informiert werden kann.

Das Property NumberOfMaps dient später dem Slider als Orientierung für den Höchstwert.

Der interessanteste Punkt an dieser Klasse dürfte allerdings das Property CurrentSliderIndex sein. Dieses wird später an den Slider gebunden. Wenn sich dessen Wert ändert, ändert es sich auch. Und in seinem Setter wird die aktuelle Map ebenso gleich gesetzt.

Zur Vollständigkeit halber einmal wieder die RelayCommand-Klasse, die sich im Aufbau aber nicht geändert hat:

using System;
using System.Windows.Input;

namespace DoorKeyManager
{
  public class RelayCommand : ICommand
  {
    private readonly Predicate<object> _canExecute;

    private readonly Action<object> _execute;

    public RelayCommand(Action<object> execute)
      : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
      _execute = execute ?? throw new ArgumentNullException("execute");
      _canExecute = canExecute;
    }

    public bool CanExecute(object parameters)
      => _canExecute == null ? true : _canExecute(parameters);

    public event EventHandler CanExecuteChanged
    {
      add
      {
        CommandManager.RequerySuggested += value;
      }
      remove
      {
        CommandManager.RequerySuggested -= value;
      }
    }
  
    public void Execute(object parameters) => _execute(parameters);
  }
}
1
regex9  30.03.2019, 02:12
@Nevron

Zu guter Letzt das View (XAML):

<Window x:Class="DoorKeyManager.MainWindow"
  xmlns="schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:local="clr-namespace:DoorKeyManager"
  mc:Ignorable="d"
  Title="Door Key Manager" Height="450" Width="800">
  <Window.DataContext>
    <local:DoorViewModel />
  </Window.DataContext>
  <Window.Resources>
    <local:ImageUrlToImageConverter x:Key="ImageUrlToImageConverter" />
  </Window.Resources>
  <Grid Name="Sketch">
    <Grid.RowDefinitions>
      <RowDefinition Height="0.9*" />
      <RowDefinition Height="0.1*" />
    </Grid.RowDefinitions>
    <ItemsControl DataContext="{Binding CurrentMap}" ItemsSource="{Binding Doors}">
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <Canvas>
            <Canvas.Background>
              <ImageBrush ImageSource="{Binding ImageUrl, Converter={StaticResource ImageUrlToImageConverter}}" />
            </Canvas.Background>
          </Canvas>
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
          <Setter Property="Canvas.Left" Value="{Binding X}" />
          <Setter Property="Canvas.Top" Value="{Binding Y}" />
        </Style>
      </ItemsControl.ItemContainerStyle>
      <ItemsControl.ItemTemplate>
        <DataTemplate>
          <Button Command="{Binding Path=DataContext.AddDoorCommand, ElementName=Sketch}" Content="{Binding Path=Name}"/>
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>
    <Slider Grid.Row="1" IsSnapToTickEnabled="True" Margin="10" Maximum="{Binding NumberOfMaps}" Minimum="0" TickFrequency="1" Value="{Binding CurrentSliderIndex}" />
  </Grid>
</Window>

Der erste interessante Punkt ist die Ressource, die ich für das Window einführe. Dies ist die Converter-Klasse, die somit eine Instanz bekommt, die im XAML-Code (als statische Ressource via Key) ansprechbar ist. Beim Binding für den ImageBrush wird der Converter dann eingesetzt:

ImageSource="{Binding ImageUrl, Converter={StaticResource ImageUrlToImageConverter}}"

Zwei weitere Änderungen:

a) Das ItemsControl nutzt nun die aktuelle Map als Datenkontext:

<ItemsControl DataContext="{Binding CurrentMap}" ItemsSource="{Binding Doors}">

So kann es auch wieder auf die Collection an Doors zurückgreifen.

b) Damit der Button trotzdem noch den richtigen Datenkontext nutzt, in dem der AddDoorCommand definiert ist, wird das Grid mit Sketch benannt.

<Grid Name="Sketch">

Und zu guter Letzt noch ein Blick auf den Slider (ohne uninteressante Attribute):

<Slider IsSnapToTickEnabled="True" Maximum="{Binding NumberOfMaps}" Minimum="0" TickFrequency="1" Value="{Binding CurrentSliderIndex}" />

Dieser hat seine Grenzen zwischen 0 und der Anzahl an verfügbaren Maps (beide Grenzwerte inklusive). Damit die Nutzereingabe nicht abweichen kann, kann der Slider nur in Einerschritten bewegt werden, dafür sorgen die TickFrequency und IsSnapToTickEnabled. Der Slider-Value wird wie schon eben erwähnt, an das Property CurrentSliderIndex gebunden. Ändert sich der Wert des Sliders, ändert sich auch das Property.

PS.: Da der GF-Editor mit URLs im Code-Snippet nicht zurechtkommt, musste ich bei den Namespaces oben überall das http:// entfernen.

1
Nevron 
Fragesteller
 30.03.2019, 12:49
@regex9

Erstmal vielen Dank für deinen ausführlichen Post. Was mir Hoffnung ist, dass ich es von der Denkweise genauso machen wollte. Aber es scheiterte im Detail.

Deinen Text gucke ich mir später intensiv an. Wollt aber trotzdem schon etwas dazu schreiben.

  1. Ich hatte mich zu sehr auf das SliderChangeValue-Event versteift. Ich hatte da gestern immer eine Exeption beim compilieren bekommen, aber nur mit einer Zeilenangabe und dass etwas nicht mit dem Binding stimmt.
  2. Bin ich vermutlich auch etwas im Chaos versunken, was meinen Code anbelangt. Durch ständiges testen ect, wurde er ziemlich verschandelt.

Zum Verständnis ist es mir aber nochmal wichtig, alles zu rekapitulieren:

  • ItemsContainerStyle: Visualisiert Objekte aus der ItemsSource in der gewünschten Form. Dabei kann man über die Setter Propertys, wie der Name schon sagt, auf die Eigenschaften des Objektes zugreifen.
  • ItemsSource: Kann komplexe Objekte beinhalten. Siehe SetterPropertys
  • DataContext: Kann nur einfache Objekte beinhalten. beispielsweise strings ect.
  • ItemsPanelTemplate: Legt lediglich die Visualisierung fest. In dem Fall Panels. Die z.b. Horizontal und Vertikal angelegt werden können.
 <Button Command="{Binding Path=DataContext.AddDoorCommand, ElementName=Sketch}" Content="{Binding Path=Name}"/>

Dabei fügst du ein Event hinzu, dass dem DataContext der ItemsSource hinzugefügt wird. Du musst quasi der höchsten Stelle sagen, dass du das tun willst. In Wirklichkeit, willst du aber die SketchFläche ansprechen, mit Elementname = Sketch, da du ja da die Buttons hinzufügen willst.

  • Slider Value: Ich war damit zu sehr beschäftigt, den Value Wert im Code zu ermitteln. Aber wie du schon sagtest: Durch das TickPlacement, werden nur ganze Zahlen genommen. Das hatte ich auch schon in meinem Code. War nur zur blöd, diesen Value Wert einfach zu nutzen.
  <ImageBrush ImageSource="{Binding ImageUrl, Converter={StaticResource ImageUrlToImageConverter}}" />

Das hab ich noch nicht so verstanden, warum das du das tust. Aber damit befasse ich mich heute Abend. Ich vermute mal, du nimmst ein Bild und hast dir einen eigenen Konverter gebaut, der ein Bild aus einer URL in ein Image konvertiert. Aber wie gesagt, guck ich mir später an und werde deine Schriften studieren.

Dein Code sieht wie immer sehr professionell aus, daher gehe davon aus, dass du das beruflich machst, oder ambitionierter Hobbyprogrammierer :) Ich hoffe bald entlassen zu werden, dann steht meiner Umschulung zum Fachinformatiker in Anwendungsentwicklung nichts mehr im Weg.

Vielleicht geht mir das auch irgendwann so leicht von der Hand wie dir. Wenn ich überlege, dass du das in der Nacht auf die schnelle hingezaubert hast.

0
regex9  30.03.2019, 14:35
@Nevron
DataContext: Kann nur einfache Objekte beinhalten. (...)

Nein. Der DataContext gibt an, in welcher Klasse ein Element (und seine Kindelemente) nach Properties suchen soll, die man in den Bindings verwendet. Für Kindelemente eines Elements, welches ein ItemsSource-Property besitzt, ändert sich der Kontext nochmals. Sie zeigen auf die Klasse des gebundenen Properties.

Einfachstes Beispiel:

<ListBox ItemsSource="{Binding Persons}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel>
        <Label Content="{Binding Name}" />
        <Label Content="{Binding Age}" />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Diese ListBox verweist auf eine Liste an Personen, die in der Klasse liegt, auf die der DataContext gerade liegt. Innerhalb der ListBox wird durch die gebundene Source der DataContext auf die Klasse Person gesetzt. So ist es möglich, die Properties Name und Age zu binden. Bei Erstellen der ListBox werden also so viele ListBoxItem-Objekte erstellt, wie es Person-Objekte in Persons gibt. Jedes ListBoxItem verwendet die Properties des Person-Objekts, zu dem es assoziativ erstellt wurde.

Dabei fügst du ein Event hinzu, dass dem DataContext der ItemsSource hinzugefügt wird. (...) In Wirklichkeit, willst du aber die SketchFläche ansprechen, (...)

Das ist auch nicht korrekt.

Der Button hat in seiner Position den DataContext auf die Klasse Door. Daher funktioniert auch das Binding auf Name (Content-Attribut). Nun soll er noch einen Command zugewiesen bekommen. Der Command AddButtonCommand liegt aber in einem völlig anderen DataContext bzw. einer anderen Klasse - dem DoorViewModel. Um den DataContext nur für dieses eine Binding zu wechseln und ohne das Binding des Content-Attributs zu beeinflussen, wird auf ein anderes Element im XAML-Tree referenziert, welches hierfür den richtigen DataContext hat.

1
Nevron 
Fragesteller
 30.03.2019, 16:23
@regex9

Wie lange programmierst du schon? Bzw wie lange hast du gebraucht, um das Level zu erreichen das du hast? Ich weiß, die Frage schwierig, da man nie auslernt.

Aber halt so ungefähr.

0
regex9  31.03.2019, 07:27
@Nevron

Für WPF hat es vielleicht zwei Jahre gebraucht, da ich sogleich an zwei Projekten beteiligt war, in denen es darum ging, zum einen die Oberfläche passend zu gestalten (animierte UI mit XAML) und zum anderen, neue Oberflächenkomponenten zu entwickeln, um damit die UI einer größeren Anwendung zu optimieren. So hatte ich früh die Gelegenheit, mich mit Triggern, Converters, Ressourcenbibliotheken, Bindings, Templates, etc. tiefer zu beschäftigen. Als Lernquelle diente noch die alte MSDN-Dokumentation. Bücher sind an dieser Stelle aber generell ebenfalls eine gute Wahl.

1
Nevron 
Fragesteller
 31.03.2019, 10:43
@regex9

Ich kann zwar Englisch, aber wenn es um so fachliche Angelegenheit geht, ist mir Deutsch natürlich lieber. Und ich muss schon sagen, das die Übersetzung der MSDN mehr als schlecht ist. Habe aber den Eindruck, dass die nur bei WPF spezifischen Themen so schlecht ist.

Aber kann mich täuschen..

0
regex9  31.03.2019, 15:54
@Nevron

Sowohl alte, als auch neue Dokumentation sind für Deutsch autogeneriert. Daher ist die Übersetzung so mangelhaft.

1
Nevron 
Fragesteller
 03.04.2019, 22:28
@regex9

So, musste die Tage wieder arbeiten. Aber heute Abend hab ich jetzt angefangen deinen Text durchzuarbeiten. Ich will ja nix kopieren, sondern verstehen.

Und ich muss leider sagen, dass es des öfteren nicht geklappt hat, weil die falschen Property Namen in den Bindings standen. Der Slider hatte bei mir nicht funktioniert, weil ich eine andere Property dort stehe hatte. Eine die von diversen Tests noch übrig war und ich vergessen hatte, den Namen zu ändern.

Dieses nameOf ist mir neu und habe es doch sofort lieben gelernt. Diese sogenannten Magic Strings kenn ich auch von Unity. Eine beachtliche Fehlerquelle. Einmal vertippt oder verguckt..und Syntax Highlighting gibts natürlich nicht an der Stelle.

Ich muss sagen, dass fuchst mich gerade. Aber dieser Fehler passiert mir nie wieder.

0
Nevron 
Fragesteller
 03.04.2019, 23:17
@Nevron

Ich hab mir ältere Versionen, die evtl 1 Tag älter sind als die Version, an der ich zu letzt gearbeitet habe angesehen.. Es hat sich leider bestätigt.

2 Strings im XAML Code waren falsch.

Ärgerlich..Ich will nicht wissen, wieviel Zeit deswegen drauf gegangen ist.

0
regex9  13.04.2019, 18:05
@Nevron

Gibt es nun noch Probleme? So wie ich das gelesen habe, scheint es bei dir doch inzwischen zu laufen?

Wenn du auf GF kommentierst, musst du darauf achten, direkt auf einen Kommentar von der Person zu kommentieren, die du anschreiben möchtest. Andernfalls wird ihr keine entsprechende Mitteilung geschickt.

0
Nevron 
Fragesteller
 14.04.2019, 14:17
@regex9

Ja, im groben läuft es. Jetzt müssen noch meine Dinge die ich im Sinn hatte eingebaut werden.

Es hatten sich noch ein paar Fragen aufgetan und hatte dafür neue Fragen gestartet, damit es auch für andere leichter wird. Deshalb hatte ich nicht hier rein geschrieben.

Ich bin ein sehr direkter Mensch. Auch obwohl du der einzige zu sein scheint, der im Thema WPF hier unterwegs zu sein scheint, kann ich nicht erwarten, dass du dich zu jedem Thema äußerst. Trotzdem habe ich es.

Ich hatte etwas in den Java Themen rumgestöbert und dabei einen Post von dir gesehen, wo du dich enttäuscht geäußert hattest. Da ging es um OOP.

Ich hatte dann gedacht, dass sich evtl einer meiner Fragen Ähnlichkeiten mit einer anderen Sache hatte wo ich hatte und bereits beantwortet hattest. Da dachte ich mit deinem ignorieren willst du mir sagen, dass dass ich es schon wissen sollte.

Naja und dann dachte ich, dass du auch von mir enttäuscht warst. Was mich am meisten gewurmt hatte und die Frage eigentlich in den Hintergrund rückte.

Klingt verrückt? Ist es vielleicht sogar. Aber so bin ich halt.

Du machst hier einen super Job und jede Community kann sich glücklich schätzen, so jemanden wie dich zu haben. Eigentlich müsstest du Geld dafür bekommen. Aber ich weiß, sowas würde dich beleidigen. Personen wie du, bringen Projekte und eine Sache hinter der sie stehen erst voran. Das ist kein schleimen, damit du auf meine Fragen antwortest, sondern ein dickes Lob von jemanden, der vielleicht schon das Handtuch ohne dich geschmissen hätte. Siehe Thema MVVM.

0
regex9  14.04.2019, 16:36
@Nevron
(...) und hatte dafür neue Fragen gestartet (...)

Die hatte ich bisher nicht gesehen, schau ich mir demnächst aber einmal an. Generell nutze ich bei GF Filter. Eine Frage, die mit programmieren getaggt ist, bekomme ich so bspw. zu sehen.

0
Nevron 
Fragesteller
 19.04.2019, 19:17
@regex9

Ja, ich hatte das Tag "programmieren" diesmal nicht dabei :)

Logbuch: Tag X

Soweit geht alles mit meinem Programm. Ich hab es aber Anfangs anders gemacht, wie du weiter oben beschrieben hast. Deswegen hat es jetzt etwas länger gedauert.

Das hatte den Grund, weil ich wissen wollte, warum du es nicht so gemacht hast wie ich es in meiner Vorstellung hatte. Ich wollte einfach mal sehen, wie langsam etwas werden kann und warum du manches tust und welche Vorteile man dadurch hat. Während der Bearbeitung wurde mir dann auch schnell klar, warum du die URI Packages nutzt. Ich wusste zwar schon warum, wollte aber mal sehen, wie unperformant so eine Geschichte werden kann, wenn man direkt Bilder in die Datenbank lädt. Anfangs hatte es zwar sehr gut funktioniert. Allerdings dann hatte ich ein etwas größeres Bild genommen und dann fingen die Probleme an. Wollte einfach mal einen Maßstab haben.

Habe auch viel über URI's und deren Vorteile gelernt. Eine nette Sache. Genau wie der Converter. Da muss ich mich aber noch weiter belesen, weil ich dazu viele offene Fragen habe. Aber das läuft alles.

Ansonsten funktioniert alles, und ich habe sogar die Sache so dynamisch gestaltet, dass man sogar selbst Bilder in die Datenbank bzw ins Programm laden kann. Kurz: Man muss eigentlich gar nicht mehr in den Quellcode oder die Datenbank gehen. Lässt sich alles innerhalb des Programms machen.

Aktuell befasse ich mich grad mehr mit Datenbanken, da ich auch eine Voll Text Suche implementieren möchte. Kämpfe aber gerade mit ein paar SQL-Express, das von den Möglichkeiten ein Monster ist und mindestens genauso viel Fehlerquellen liefert :) Und dann kommen noch die verschiedenen Benutzerkonten und die damit verbundenen Zugriffsrechte.

0
Nevron 
Fragesteller
 20.04.2019, 20:02
@regex9

Mir ist da jetzt ein Problem aufgefallen, da ich gerade daran bin, dass ganze auszuarbeiten und zu erweitern.

Thema Itemssource und DataContext..:

Es geht um diese Stelle:

<ItemsControl DataContext="{Binding CurrentMap}" ItemsSource="{Binding Doors}">

Ich weiß dass der DataContext angibt, in welcher Klasse die Properties suchen soll. Die Itemssource hält die ObservableCollection für mein DataTemplate bereit bzw damit halt die Buttons erstellt werden.

Aber leider nur eines von beiden. Das ist bestimmt ein ähnlicher Fall vom Prinzip, wie:

  <Button Command="{Binding Path=DataContext.AddDoorCommand, ElementName=Sketch}"

Es verhält sich so:

Lösche ich den DataContext werden mir die Türen Ordnungsgemäß angezeigt. Hab ich in der Zeile den DataContext UND die Itemsource mit angegeben, seh ich zwar das Bild aber keine Türen mehr.

Ich dachte eigentlich ich hab das verstanden..... Aber offenbar... hab ich es nicht, oder es ist irgendwo ein ganz dummer Fehler.

Hier noch schnell meine Field Klasse. Das entspricht der Map Klasse in deinem Beispiel:

 public class Field
  {
    public int Feldnummer { get; set; }
    public string PicsOfFieldsUri { get; set; }
    public ObservableCollection<Door> DoorList { get; set; }

  }
0
regex9  20.04.2019, 20:32
@Nevron

Bei meinem Beispiel gab es auch eine Strukturänderung, die du scheinbar nur z.T. durchgeführt hast (Woher sonst kommt das Property Doors bei dir?). Ich habe festgelegt, dass jede Karte (Map) eine bestimmte Anzahl an Türen hält. Bei dir heißt die Klasse offensichtlich Field und sie hält das Property DoorList.

Das Property CurrentMap (sollte bei dir wohl CurrentField heißen) muss im ViewModel liegen und auf die aktuelle Karte verweisen, die angezeigt werden soll.

class YourViewModelClass : INotifyPropertyChanged
{
  // ...

  public Field CurrentField { /* ... */ }

  // ...
}

Dein ItemsControl sollte mit seiner ItemsSource auf die DoorList zeigen.

<ItemsControl DataContext="{Binding CurrentField}" ItemsSource="{Binding DoorList}">

PS.: Feldnummer sollte Number oder Id heißen. Dann ist es Englisch, erfüllt den Pascal-Case-Stil und enthält keine doppelten Informationen (Field.Field...). Das Property PicsOfFieldsUri würde ich ebenfalls umbenennen (z.B. PicturePath), denn es ist ein String (kein URI) und sollte in der Einzahl vorliegen.

0
Nevron 
Fragesteller
 20.04.2019, 21:03
@regex9

Mein ItemControl zeigt bereits auch auf die DoorList.

Aber es wird nix angezeigt. Es gibt auch keine Fehler oder so.

Diese Felder. Der Betrieb ist in Felder unterteilt. Das kannst du dir so vorstellen wie ein Parkplatz wo die Parkbuchten nebeneinander liegen.

Ich habe zwei Tables. Einmal die Fields und einmal die Door Klasse. Anhand bestimmter Merkmale werden je nach Feldnummer die jeweiligen Türen mit eingezeichnet. Das hängt ebenfalls mit am SliderIndex.

Meine Door Klasse sieht so aus:

public class Door
  {
    public int Nummer { get; set; }
    public string Bezeichnung { get; set; }
    public string Ort { get; set; }
    public string Maße { get; set; }
    public int Schrank { get; set; }
    public int Reserve { get; set; }
    public int Umlauf { get; set; }
    public string Personen { get; set; }
    public int Xcoordinate { get; set; }
    public int Ycoordinate { get; set; }
    public int Feldnummer { get; set; }

  }

Bis auf die Koordinaten sind alles Betriebspezifische Inhalte.

ICh hab es auch im Debugger getestet. Der Codebehind tut was er soll. Es liegt also an dem Binding. Der DataContext und das ItemsControl stören sich gegenseitig. Bevor ich es so gemacht hatte wie du es vorgeschlagen hast, hatte ich eine ObservableCollection im VM. Was ja eigentlich nicht gewünscht ist wegen dem MVVM. Aber da ging es auch nur, wenn der DataContext nicht da war.

Ich guck nochmal. Evtl hab ich was übersehen.

0
regex9  20.04.2019, 21:36
@Nevron

Deine bisherigen Ausführungen machen mich etwas stutzig. Mit Tables meinst du sicherlich Datenbanktabellen?

Diese Anforderungen müssen erfüllt sein, damit das Binding funktioniert:

  • Das View muss das ViewModel als DataContext gesetzt haben.
  • Das ViewModel muss ein Property besitzen, welches auf das Feld verweist, welches aktuell angezeigt werden soll. Dieses Property muss vom INotifyPropertyChanged-Interface Gebrauch machen.
  • Der Typ des eben genannten Properties muss eine Liste an Door-Objekten halten - diese Anforderung hast du auf jeden Fall erfüllt. Gesichert muss sein, dass die Liste auch wirklich befüllt ist / wird.

Wenn du bei dir den DataContext entfernen musst, damit es funktioniert, deutet es daraufhin, dass dein DataContext bereits auf der Field-Klasse liegt.

0
Nevron 
Fragesteller
 20.04.2019, 22:03
@regex9
@Nevron
Deine bisherigen Ausführungen machen mich etwas stutzig. Mit  Tables meinst du sicherlich Datenbanktabellen?

Jup. In der einen Table sind die Bildpfade und die Feldnummer, in der anderen Tabelle die Schlüsseldaten(Door Klasse).

Und ja, so gesehen verweist der DataContext bereits auf die Klasse Field.

Aus dem Projekt:

XAML:

 DataContext="{Binding CurrentField}" ItemsSource="{Binding DoorList}"

VM:

  private Field currentField;
    public Field CurrentField
    {
      get { return currentField; }
      set
      {       
        currentField = value;
        ChangeProperty(nameof(CurrentField));        
      }
    }

Die Daten sind auf jedenfall in der ObservableCollection. Das hab ich mit dem Debugger überprüft. Das läuft wie es soll. Schade das man nicht mit dem Debugger sehen kann, was im XAML Code passiert. Das wär es :)

Wenn du bei dir den DataContext entfernen musst, damit es funktioniert, deutet es daraufhin, dass dein DataContext bereits auf der  Field-Klasse liegt.

Das wird es sein. Das hatte ich auch schon versucht. Ich hab sogar mal eine ObservableCollection separiert mit den Door-Daten.

Allerdings dann kommt die Meldung in der Ausgabe:

System.Windows.Data Error: 40 : BindingExpression path error: 'Tueren' property not found on 'object' ''Field' (HashCode=1101770)'. BindingExpression:Path=Tueren; DataItem='Field' (HashCode=1101770); target element is 'ItemsControl' (Name='myIc'); target property is 'ItemsSource' (type 'IEnumerable')

Tueren ist einfach eine Observable wie die DoorList. Nur das es im VM steht.

0
regex9  20.04.2019, 23:13
@Nevron

Kannst du mal dein ViewModel und deinen XAML-Code posten? Besser via pastebin, um das Scrollrad auf dieser Seite etwas zu schonen.

0
regex9  20.04.2019, 23:57
@Nevron

Mir fällt zunächst auf, dass du im XAML-Code keinen DataContext für das Window definierst. Folglich machst du dies wohl im xaml.cs (was im Übrigen auch ok wäre):

DataContext = new Mittelebene();

Soetwas:

private ObservableCollection<Door> tuerenList { get; set; }

public ObservableCollection<Door> Tueren
{
  get { return tuerenList; }
  set
  {
    tuerenList = value;
    ChangeProperty("Tueren");
  }
}

ist unnötig.

Das Property reicht aus:

public ObservableCollection<Door> Tueren { get; set; }

zudem muss es irgendwo initialisiert und befüllt werden.

Da der DataContext von dem ItemsControl auf CurrentField liegt, wird auch im dem Typ, den das Property hat (Field) nach einem Property gesucht, welches Tueren heißt. Das existiert aber nicht. Wenn du das DataContext-Attribut entfernst, wird vermutlich im ViewModel gesucht, was wiederum zu Erfolg führt.

Was sich mir nun insgesamt noch nicht ganz erschließt ist, wie du die Türen wirklich lädst. Werden sie in die DoorList eingetragen, wenn ein Field erzeugt wird oder lädst du sie tatsächlich separat, wie man es in ActuallySliderValue sieht? Du hast darüber Test geschrieben, es nun aber schon öfter angedeutet. Weg 1 wäre die Wahl, da Door und Field in direkter Abhängigkeit zueinanderstehen und das Model beschreiben. Das Initialisieren der Field-Objekte mitsamt ihrer Eigenschaften gehört ins Model.

0
Nevron 
Fragesteller
 21.04.2019, 00:12
@regex9

DIe werden erzeugt, wenn der Slider bewegt wird. Der Index des Sliders gibt an, welche Türen geladen werden. Bzw wird dann so sein, zu erst muss es funktionieren.

Wenn ich den DataContext lösche, dann funktioniert es. Aber dann werden plötzlich keine Bilder mehr angezeigt, da ja da die Field-Klasse gefragt ist. Das heißt ich muss es irgendwie schaffen, dass der DataContext die Itemssource sich gegenseitig nicht in die quere kommen.

https://www.bilder-upload.eu/bild-dd69ce-1555798334.png.html

0
regex9  21.04.2019, 00:23
@Nevron
DIe werden erzeugt, wenn der Slider bewegt wird.

Wieso? Als lazy loading-Prinzip? Zuvor müsste zumindest geprüft werden, ob das CurrentField nicht schon Türen in seiner Liste hat.

Das heißt ich muss es irgendwie schaffen (...)

Nimm mein obiges Beispiel und bau auf diesem auf. Ich denke, dies ist an dieser Stelle doch der effektivere Weg.

0
Nevron 
Fragesteller
 21.04.2019, 16:15
@regex9

Ich glaube ich hab jetzt mal selbst den Weg aus dem Jammertal gefunden.

Bevor ich die PicPaths in der DB gespeichert hatte, hatte ich ja die Bilder dort abgespeichert. Da ging auch alles. Bild + Türen. Erst als ich übergegangen bin die Bildpfade zu speichern, hatte es nicht mehr funktioniert.

Mir ist die Idee gleich gekommen, als ich heute früh aufgestanden bin :) 12 Stunden am Stück programmieren ist halt doch keine so gute Idee. Hab es dann klar vor Augen gesehen.

Der Fehler lag nicht im DataContext oder der Itemssource. Er lag an der gebundenen Property für den PicPath. Das hatte ich nämlich damals geändert, da mir der Converter den Fehler ausspuckte, dass die Value ein string sein muss. Die Exeption hat er bereits beim Start der Application geworfen.

Über den Debugger hab ich rausgefunden, dass der string null war. Was logisch ist, da ja beim Start des Programms in der Property noch kein Information bereit stand. Darauf hin habe ich den Converter etwas umgeschrieben, worauf es dann funktioniert hatte. Hatte einfach noch mit hinzugefügt

if(value == null
Bild = Field am Index 0

Jetzt fragst du dich bestimmt:

Warum hat der Esel nicht einfach in den Konstruktor des VM den aktuellen SliderIndex auf 0 gesetzt, so wie ich es geschrieben hatte?

Kein Kommentar. Der Converter hab ich wieder in den Ursprungszustand zurückgebracht und den Sliderindex im Konstruktor auf 0 gesetzt. :)

War eine Verkettung von 2 kleineren Fehlern. Aber der Lerneffekt ist groß gewesen. Das passiert mir auch nicht mehr..

Jetzt geht alles.

Wollte aber noch kurz das ansprechen:

Wieso? Als  lazy loading-Prinzip? Zuvor müsste zumindest geprüft werden, ob das CurrentFieldnicht schon Türen in seiner Liste hat.

Ich hatte mich da evtl nicht klar ausgedrückt. Die Fieldmaps, also die Datenbank mit den PicPaths die sollen natürlich nur einmal geladen werden.

Die andere Tabelle, also die mit den Türinformationen, wollte ich aberjedesmal neu abrufen, damit gewährleistet ist, dass die Daten auch immer sofort akutalisiert dem Nutzer zur Verfügung stehen.

Und jetzt: Säubere ich erstmal das Programm dem ganzen Test-Geraffel, das mittlerweile fast größer als das eigentliche Programm ist.

0
regex9  21.04.2019, 16:22
@Nevron
Die andere Tabelle, (...) wollte ich aberjedesmal neu abrufen, damit gewährleistet ist, dass die Daten auch immer sofort akutalisiert (...) zur Verfügung stehen.

Es gibt noch eine andere Eingabequelle, die die Datenbank ändert? Ich dachte, dies täte nur noch deine Anwendung.

0
Nevron 
Fragesteller
 21.04.2019, 17:57
@regex9

Ja, genau.

Der Nutzer soll die Daten aus der Door Klasse ändern können. Daten hinzufügen, löschen ect. Daher ist es wichtig, dass die Daten nach einer Änderung anschließend auch geupdated angezeigt werden. Das wird dann ja alles über die Querys geregelt.

Mir ist natürlich bewusst, dass wenn der Nutzer keine Änderungen vornimmt, dass das dann mehr oder weniger unnötig ist.

Hmm.

Evlt rufe ich alle Daten ab und nur wenn ein Datensatz über eine Query geupdated wird, bzw wenn erst tatsächlich eine Änderung vorgenommen wird. rufe ich die Daten nochmal ab.

Das würde Performance sparen und es würde nur eine Verbindung zum Server aufgebaut werden, wenn diese auch tatsächlich gebraucht werden würde. Das würde unnötige Verbindungen vermeiden und das ganze auch sicherer machen.

Bis jetzt sollte der Nutzer die Datensätze über die Textboxen ändern können. Aber ich muss sagen, dass mich der Gedanke beflügelt, wenn der Nutzer Datensätze ändern kann, in dem er in das Datengrid klickt und einfach von dort aus ändern könnte. Die Steuerung wäre intuitiver und das Design aufgeräumter.

0
regex9  21.04.2019, 18:22
@Nevron
Daher ist es wichtig, dass die Daten nach einer Änderung anschließend auch geupdated angezeigt werden.

Demnach ist es eine Anforderung, dass mehrere Personen gleichzeitig mit einer Instanz deines Programmes Karten derselben Datenbank laden und verändern können?

Ansonsten übernimmt doch das Programm (bzw. die View mitsamt dem Binding zur ObservableCollection) die Aktualisierung der grafischen Oberfläche für dich, bzw. wenn sich ein Door-Objekt ändert, wäre ein erneutes Event-Listening notwendig. Deine Model-Objekte halten stets den neuesten Stand und die Datenbank muss nur zu Programmstart angefragt werden. Wann diese neuen Daten in der Datenbank wieder gespeichert / aktualisiert werden, kann man sich strategisch betrachten. Entweder sofort nach einer Änderung oder bspw. erst vor Schließen der Anwendung / bei Klick auf einen Save-Button / o.ä..

0
Nevron 
Fragesteller
 21.04.2019, 19:38
@regex9

Das hab ich verstanden und war auch so angedacht. Das Nutzerverhalten wuerde nach einer sofortigen speichern verlangen und das betrachten der Änderung verlangen.

Die Türen sind ja statisch in einem Gebäude. Aber wenn jemand den zylinder beispielsweise ausbaut und wo anders einbaut ist es wichtig das die Änderungen sofort übernommen werden sollen, da zylinder z. B. An einen Bestand geknüpft werden.

Evtl wäre es ratsam gewesen ein uml diagram zu erstellen. Meine Ideen und Vorstellungen wachsen mit dem Programm mit. Das Problem ist, das einige Vorstellungen und Ideen auf andere Dinge aufbauen und dann mich wieder aus dem Konzept bringen. Aber habe halt hohe Ansprüche und würde nur etwas machen, wo mir auch selbst passen würde. Aber da verrennt man sich svhnell

0