Laden...

WPF - Zwei verschiedene DataSources

Erstellt von Sebastian1989101 vor 12 Jahren Letzter Beitrag vor 12 Jahren 4.759 Views
Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren
WPF - Zwei verschiedene DataSources

Hallo Community,

ich komme mal direkt zum Problem:


<Liste ItemSource="{Binding IrgendeineObersableCollectionSource}">
  <Template etc>
    <TextBox Property1="{Binding PropertyXY}" Property2="{Binding Property Z}" />
  </Template etc>
</liste>

so ich hoffe der Pseudocode ist verständlich. Problem ist das die beiden Properties, XY und Z aus zwei verschiedenen Quellen stammen - XY stammt aus der ItemSource der Liste und Z stammt aus dem MainViewModel (MVVM Pattern) des Fensters. XY Funktioniert auch soweit, nur leider zeigt Z seit dem Umbau auf der Liste keine Reaktion mehr (vorher ging alles!).

Ein Punkt ist unter anderen das auch mehrere Elemente auf Property Z schreiben durfen - Z soll nur den aktuell Makierten Text beinhalten, wärend XY der Text ist. Ich hoffe es kann mir jemand ein paar tipps geben oder mir sagen wie dies zu lösen ist, den ich bin so langsam ratlos =(

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

I
302 Beiträge seit 2008
vor 12 Jahren

Kannst nicht ein DTO/ViewModel daraus machen?

Bei MVVM sollte nach meiner Auffassung das ViewModel bereits für die Sicht aufbereitet sein. Mir ist kein Weg bekannt mehrere Sourcen zu binden - wozu auch?

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

Es wäre mit möglich das DTO und ViewModel zu verbinden, allerdings hätte ich den immer noch dass Problem das versucht wird in einer Eigenschaft eines Listen Elements zu schreiben anstatt in ein Globales zusammen gefasstes.

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

1.378 Beiträge seit 2006
vor 12 Jahren

Hallo Sebastian1989101,

du musst mittels RelativeSource dir das MainWindow suchen und kannst dann auf den DataContext dessen mit Path=DataContext.... zugreifen.

Lg, XXX

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

Das versuchten wir bereits allerdings bisher vergeblich 😃

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

1.378 Beiträge seit 2006
vor 12 Jahren

Naja und jetzt? Poste doch bisschen mehr Code... Am besten die komplette Hierarchie bis zum Fenster rauf und wo welcher DataContext verwendet werden soll.

Lg XXX

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

Gut den hier mal das komplette Grid wo es rein soll:


     <Grid Name="Root">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        
        <Menu Grid.Row="0">
            <UserControls:ConnectMenuItem Style="{DynamicResource ConnectMenu}" Command="{Binding ConnectDisconnectCommand}" />
            <MenuItem Header="Send Command" Command="{Binding SendCommand}" />
            
            <MenuItem Header="Settings">
                <MenuItem Header="Connection Settings" Command="{Binding DisplayConnectionSettingsCommand}" />
                <MenuItem Header="Swap Bytes" IsCheckable="True" IsChecked="{Binding DataTransferModel.SwapBits}" Click="MenuItem_Click" />
            </MenuItem>
        </Menu>

        <ListView ItemsSource="{Binding DataTransferModel.Output}" Background="Transparent" Margin="0" VerticalContentAlignment="Top" AlternationCount="2" 
                  Name="lvOutput" ScrollViewer.VerticalScrollBarVisibility="Auto" Grid.Row="2">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="0,1">
                        <TextBox Text="{Binding Path=DateTime, Mode=OneWay}" Margin="0" ToolTip="Time" />
                        <TextBox Text="{Binding Type, Mode=OneWay}" ToolTip="Send or Recived data" />
                        <TextBox Text="{Binding Preamble, Mode=OneWay}" ToolTip="Count of preambles" />
                        <TextBox Text="{Binding Address, Mode=OneWay}" ToolTip="Address" />
                        <TextBox Text="{Binding Command, Mode=OneWay}" Width="27" HorizontalContentAlignment="Center" ToolTip="Command number" />
                        <TextBox Text="{Binding ByteCount, Mode=OneWay}" ToolTip="Byte count" />
                        
                        <UserControls:OutputTextBox Text="{Binding Data, Mode=OneWay}" BorderThickness="0" Background="Transparent" Margin="0" VerticalContentAlignment="Top" 
                                                    VerticalScrollBarVisibility="Auto" IsReadOnly="True" Grid.Row="2" TextWrapping="WrapWithOverflow" FontWeight="Normal" 
                                                    SelectedValue="{Binding SelectedOutput, ElementName=Root, Path=DataContext, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                                    FontFamily="Consolas" ToolTip="Recived or sended data" Foreground="#FFE58D00" />
                         
                        <TextBox Text="{Binding Response, Mode=OneWay}" ToolTip="Response code" />
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <StatusBar Grid.Row="3" Background="{StaticResource DefaultMenuBackground}">
            <TextBlock Text="{Binding Value}" Margin="5,0" TextWrapping="Wrap" MouseLeftButtonDown="MouseLeftButtonDownStatusBar" />
        </StatusBar>
    </Grid>

Das Problem besteht bei der OutputTextBox und zwar soll die Text eigenschaft - hier gebunden an "Data" soll von der Liste kommen an der auch die ListView Binded, waerend SelectedValue an einer Property binden soll welche im Haupt ViewModel definiert ist hier "SelectedOutput" - dazu ist noch zu sagen das "SelectedOutput" fuer alle eintrage in der liste gleich sein soll, es soll halt nur der zuletzt makierte text dort drin vermerkt werden fuer eine weitere verarbeitung.

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

P
660 Beiträge seit 2008
vor 12 Jahren

Ich gehe mal davon aus dass das VM bereits dem DataContext zugewiesen worden ist.
in dem Fall kannst du folgendes machen:


<Liste ItemSource="{Binding IrgendeineObersableCollectionSource}">
  <Template etc>
    <TextBox Property1="{Binding PropertyXY}" Property2="{Binding ElementName=LayoutRoot, Path=DataContext.Property Z}" />
  </Template etc>
</liste>

MfG
ProGamer*Der Sinn Des Lebens Ist Es, Den Sinn Des Lebens Zu Finden! *"Wenn Unrecht zu Recht wird dann wird Widerstand zur Pflicht." *"Ignorance simplifies ANY problem." *"Stoppt die Piraterie der Musikindustrie"

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

Ja es ist zugewiesen, aber mit ElementName klappt es nciht, das hatte ich bereits versucht.

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

P
660 Beiträge seit 2008
vor 12 Jahren

Auch schon versucht ElementName wegzulassen?

MfG
ProGamer*Der Sinn Des Lebens Ist Es, Den Sinn Des Lebens Zu Finden! *"Wenn Unrecht zu Recht wird dann wird Widerstand zur Pflicht." *"Ignorance simplifies ANY problem." *"Stoppt die Piraterie der Musikindustrie"

1.378 Beiträge seit 2006
vor 12 Jahren

Also beide Varianten müssen funktionieren. RelativeSource sowie die mit ElementName. Es reicht eigentlich schon direkt an den DataContext der ListView zu binden.


"{Binding ElementName=lvOutput, Path=DataContext.SelectedOutput}"

oder


"{Binding RelativeSource={RelativeSource AncestoryType={x:Type ListView}}, Path=DataContext.SelectedOutput}"

wenn dem nicht so ist, dann ist hat der entsprechende DataContext diese Eigenschaft nicht oder der Pfad dort hin ist schlichtweg falsch.

Bindingfehler werden im Debugoutput ausgegeben. Ansonsten ist es mMn. auch ratsam immer vollqualifizierte Bindingpaths anzugeben, weil diese dann eine Exception zur Laufzeit werfen wenn sie nicht gefunden werden.

Beispiel:

(ViewModels:ISowiesoViewModel.SelectedItem)

Lg XXX

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

Er gibt zwar keine Binding Fehler aus, geht aber auch nie im Setter der Property hinein. Woran koennte es den noch liegen?

Die Property in die alle eintraege schreiben sollte sieht wie folgt aus:


        private string _selectedOutput = "";
        public string SelectedOutput
        {
            get { return _selectedOutput; }
            set { _selectedOutput = value; SelectedOutputChanged(); }
        }

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

1.378 Beiträge seit 2006
vor 12 Jahren

Aber im Getter schon? Ist das eine selbst definierte DependencyProperty? Dann musst du eventuell noch explizit Mode=TwoWay setzen.

Lg XXX

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

TwoWay ist gesetzt und wie grade gesehen geht er im getter rein nur im setter nicht. Die Dependency sollte die gleiche sein ..


        public static readonly DependencyProperty SelectedValueProperty =
                DependencyProperty.Register("SelectedValue",
                typeof(String), typeof(OutputTextBox), new UIPropertyMetadata());

        public string SelectedValue
        {
            get { return (string) GetValue(SelectedValueProperty); }
            set { SetValue(SelectedValueProperty, value); }
        }

Kann es sein das er vllt. den Update Trigger in Form von PropertyChanged nicht aufruft und somit das ViewModel das setten nicht mit bekommt?

ps. angegeben ist das Binding nun wie folgt:

SelectedValue="{Binding ElementName=lvOutput, Path=DataContext.SelectedOutput, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

1.378 Beiträge seit 2006
vor 12 Jahren

Ich kenn mich gerade nicht mehr aus, was genau du erreichen willst.

Du willst das SelectedOutput von dem DependencyProperty SelectedValue gesetzt wird.
Wer oder was setzt SelectedValue?

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

SelectedValue wird im SelectionChanged Event der TextBox gesetzt, da man auf SelectedText nicht Binden kann.


        public OutputTextBox()
        {
            TextChanged += ScrollToEndEventHandle;
            SelectionChanged += SelectionChangedEventHandle;
        }

        public void Dispose()
        {
            TextChanged -= ScrollToEndEventHandle;
            SelectionChanged -= SelectionChangedEventHandle;
        }

        private void SelectionChangedEventHandle(object sender, RoutedEventArgs e)
        {
            SelectedValue = SelectedText;
        }

Erreichen moechte ich das die Auswahl im ViewModel verfuegbar ist - bisher ist der stand das der getter Funktioniert, nur leider der setter nicht, warum auch immer 😕

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

1.378 Beiträge seit 2006
vor 12 Jahren

Ich hab jetzt ewig gesucht und probiert um eine Lösung zu dem Problem zu finden:

Das Problem ist hier beschrieben: Dependency Property Value Precedence

Das Problem ist, dass wenn du im Codebehind deine DependencyProperty setzt, überschreibst du damit automatisch das Binding und es wird daher kein set mehr aufgerufen.

Statt dem direkten zuweisen musst du daher einen anderen Weg nehmen:


        private void TextBoxSelectionChanged(object sender, RoutedEventArgs e)
        {
            SetCurrentValue(SelectedTextProperty, _textBox.SelectedText);
        }

Diese Methode setzt den Wert der Property ohne bestehnde Bindings zu überschreiben.

Lg, XXX

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

Ich verstehe grade nicht so ganz was du meinst. wieso im CodeBehinde? Die Dependancy ist in einen eigenen UserControl welches ich benutze und es ging vor dem Umbau auf der ListView - wo die textbox alleine stand - ja auch mit get und set, seit der ListView geht nur noch das get, kein set mehr.

Auch mit deinen Vorschlag Funktioniert es leider nicht.
Aktuell sieht das UserControl so aus:


    public class OutputTextBox : TextBox, IDisposable
    {
        public static readonly DependencyProperty SelectedValueProperty =
                DependencyProperty.Register("SelectedValue",
                typeof(String), typeof(OutputTextBox), new UIPropertyMetadata());

        public string SelectedValue
        {
            get { return (string) GetValue(SelectedValueProperty); }
            set { SetValue(SelectedValueProperty, value); }
        }

        public OutputTextBox()
        {
            TextChanged += ScrollToEndEventHandle;
            SelectionChanged += SelectionChangedEventHandle;
        }

        public void Dispose()
        {
            TextChanged -= ScrollToEndEventHandle;
            SelectionChanged -= SelectionChangedEventHandle;
        }

        private void SelectionChangedEventHandle(object sender, RoutedEventArgs e)
        {
            SelectedValue = SelectedText;
        }

        private void ScrollToEndEventHandle(object sender, TextChangedEventArgs e)
        {
            ScrollToEnd();
        }
    }

das was im SelectionCHanged passiert hatte ich kurzzzeitig fuer deinen art ersetzt, zeigte aber keinen Unterschied im Verhalten.

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

1.378 Beiträge seit 2006
vor 12 Jahren

Hier mein Beispiel wos funktioniert:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication2
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MyViewModel();
        }
    }

    public class MySelectedValueTextBox : TextBox
    {
        public string SelectedValue { get { return (string)GetValue(SelectedValueProperty); } set { SetValue(SelectedValueProperty, value); } }

        public static readonly DependencyProperty SelectedValueProperty =
            DependencyProperty.Register("SelectedValue", typeof(string), typeof(MySelectedValueTextBox),
                                                       new PropertyMetadata(""));

        protected override void OnSelectionChanged(RoutedEventArgs e)
        {
            SetCurrentValue(SelectedValueProperty, SelectedText);
        }
    }

    public class MyViewModel : INotifyPropertyChanged
    {
        public List<Person> Items { get; set; }

        public MyViewModel()
        {
            Items = new List<Person> { new Person { Name = "Max Mustermann" }, new Person { Name = "Franz Huber" } };
            SelectedText = "bla";
        }

        private string _selectedText;
        public string SelectedText
        {
            get { return _selectedText; }
            set
            {
                _selectedText = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("SelectedText"));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class Person
    {
        public string Name { get; set; }
    }
}


<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication2="clr-namespace:WpfApplication2" Title="MainWindow" Height="350" Width="525">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <TextBlock Text="{Binding Path=SelectedText}"/>

    <ListBox Name="lb" Grid.Row="1" ItemsSource="{Binding Path=Items}">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal">
            <WpfApplication2:MySelectedValueTextBox Text="{Binding Name}"
              SelectedValue="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=DataContext.SelectedText, Mode=TwoWay}"/>
            <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=DataContext.SelectedText}"/>
          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
  </Grid>
</Window>

Lg, XXX

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

Woher stammt bei dir "SetCurrentValue"? SetValue habe ich aber kein CurrentValue.
Ok ich merke schon das deine Variante klappt - liegt wirklich am SetCurrentValue, allerdings ist dies im .NET 3.5 nicht verfuegbr. 😦 Gibts alternativen?

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

1.378 Beiträge seit 2006
vor 12 Jahren

Sorry, wusste nicht, dass es die Methode nur unter .NET 4.0 gibt.

Entweder du kannst upgraden oder du musst dich mit dem Workarround mit CoerceValue zufrieden geben:Local values in DependencyObjects

Lg, XXX

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 12 Jahren

.NET 4 geht leider nicht, aber ist so viel Aufwand wie im Work Around noetig nur fuer so was? oO Erscheint mir in moment noch alles sehr kompliziert und komplex was dort geschieht. Is den alles davon soweit noetig? Oder genuegen teile davon?

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog