Laden...

UserControl: Binding Property/Command to ViewModel

Erstellt von TreaxP vor einem Jahr Letzter Beitrag vor 10 Monaten 820 Views
T
TreaxP Themenstarter:in
11 Beiträge seit 2023
vor einem Jahr
UserControl: Binding Property/Command to ViewModel

Hallo zusammen!
Wäre jemand so freundlich und würde mir zu nachfolgenden zwei Fragestellungen weiterhelfen:

Ich habe ein UserControl (OptionsRow) das verschiedene Elemente wie TextBoxen, ComboBoxen, Labels usw. beinhaltet und im MainWindow.xaml 10x verwendet wird. Nun soll sich z.B. die TextBox des UserControl1 an die Eigenschaft Option1.Eigenschaft im ViewModel (MVVM) binden, das UserControl2 an die Eigenschaft Option2.Eigenschaft usw.
Frage 1: Gelöst habe ich das Problem zwar mit nachfolgendem Code, frage mich aber ob das nicht einfacher ginge, als im MainWindow.xaml nochmals sämtliche Eigenschaften vom ViewModel an die DependencyProperties vom UserControl zu binden. Für die bisherigen Eigenschaften ist das ja kein Problem, aber es kommen noch einige weitere hinzu und ich frage mich ob das der richtige Weg ist?

MainWindow.xaml:

<uc:OptionsRow Grid.Row="0" Visibility="{Binding Option1.OptionsRowIsVisible, Converter={StaticResource booleanToVisibilityConverter}}" 
RowNumber="{Binding Option1.RowNumber}" BS="{Binding Option1.BS}" PC="  {Binding Option1.PC}" Pr="{Binding Option1.Pr}" KUl="{Binding Option1.KUl}"/>

UserControl.xaml (z.B. eine TextBox):

<TextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Foreground="Black" Text="{Binding KUl, RelativeSource={RelativeSource 		AncestorType=UserControl}, UpdateSourceTrigger=LostFocus}">
           <!--<i:Interaction.Triggers>
               <i:EventTrigger EventName="KeyDown">                    
                   <i:InvokeCommandAction Command="{Binding CalculateOptionCommand}"
                                          CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=RowNumber}"/>
               </i:EventTrigger>
           </i:Interaction.Triggers>-->
           <TextBox.InputBindings>
               <KeyBinding Key="Return" Command="{Binding CalculateOptionCommand}"
                               CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=RowNumber, Mode=OneWay}"/>
           </TextBox.InputBindings>         
       </TextBox>

UserControl.xaml.cs (z.B. die Property KUl die an die TextBox bindet)

private static readonly DependencyProperty KUlProperty = DependencyProperty.Register("KUl", typeof(float), typeof(OptionsRow), 
           new FrameworkPropertyMetadata(0.0f) { BindsTwoWayByDefault = true });
           
public float KUl
       {
           get { return (float)GetValue(KUlProperty); }
           set { SetValue(KUlProperty, value); }
       }

Frage 2: Wie bringe ich es hin, dass der Command in der TextBox mit Drücken der Enter-Taste ausgeführt wird? Mit "KeyBinding" funktioniert das zwar, aber nur wenn ich zuerst den Fokus wechsle ("LostFocus") und dann zur ursprünglichen TextBox zurückkehre und Enter drücke. "PropertyChanged" als UpdateSourceTrigger habe ich auch schon versucht, aber dann kann ich keinen Punkt oder Komma in der TextBox eingeben (die TextBox bindet KUl die ein float ist). Mit "Interaction.Triggers" von Xaml Behaviors habe ich das auch schon versucht, aber dann bringe ich es nicht hin, dass "KeyDown" nur auf Return reagiert.

Vielen Dank für eure Hilfe.

126 Beiträge seit 2023
vor einem Jahr

Auch so ein UserControl hat ein DataContext wo dann das normale Binding funktioniert.

<uc:OptionsRow Grid.Row="0" DataContext="{Binding Option1}"/>

und im UserControl dann

<TextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Foreground="Black" Text="{Binding KUl, UpdateSourceTrigger=LostFocus}">
	<TextBox.InputBindings>
    	<KeyBinding Key="Return" Command="{Binding CalculateOptionCommand}"
        	CommandParameter="{Binding RowNumber, Mode=OneWay}"/>
    </TextBox.InputBindings>
</TextBox>

Der Zugriff auf CalculateOptionCommand geht so dann natürlich nicht mehr (wegen dem geänderten DataContext).

Hat die Blume einen Knick, war der Schmetterling zu dick.

T
TreaxP Themenstarter:in
11 Beiträge seit 2023
vor einem Jahr

Vielen Dank für deine Hilfestellung!

Ursprünglich habe ich das mit dem DataContext ebenfalls so gelöst wie von dir vorgeschlagen. Da der Zugriff auf CalculateOptionCommand dann aber nicht funktionierte, habe ich das Ganze auf die von mir beschriebene Variante gelöst. Hast du mir einen Vorschlag wie ich das Problem lösen kann, damit der Zurgriff auf CalculateOptionCommand auch mit geändertem DataContext wieder funktioniert?

126 Beiträge seit 2023
vor einem Jahr

Du hast CalcualteOptionCommandvom DataContext bezogen und für die Eigenschaften von der Option Klasse hast du jeweils eine DependencyProperty erstellt.

Ich dachte das wäre klar ... denn nun bekommst du die ganzen Option Eigenschaften über den DataContext und folglich den CalculateOptionCommand ... z.B. über eine DependencyProperty. Es ist die gleiche Vorgehensweise aber nur mit vertauschten Rollen.

Denkbar wäre natürlich auch eine Erweiterung der Option Klasse um genau so eine CalculateOptionCommand Eigenschaft.

Hat die Blume einen Knick, war der Schmetterling zu dick.

126 Beiträge seit 2023
vor einem Jahr

Obwohl ich es persönlich wohl eher so lösen würde, dass das UserControl eine DependencyPropertyfürOption, CalculateCommand und CalculateCommandParameter bereitstellt.

Im Control würde das dann wie folgt gemappt:

<UserControl x:Class="UserControlBinding.WpfApp.OptionControl"
             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:local="clr-namespace:UserControlBinding.WpfApp"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             x:Name="Control"
             d:DesignHeight="450"
             d:DesignWidth="800"
             mc:Ignorable="d">
    <Grid>
        <StackPanel>
            <TextBlock Text="{Binding ElementName=Control, Path=Option.RowNumber, Mode=OneWay}" />
            <TextBlock Text="{Binding ElementName=Control, Path=Option.BS, Mode=OneWay}" />
            <TextBlock Text="{Binding ElementName=Control, Path=Option.PC, Mode=OneWay}" />
            <TextBlock Text="{Binding ElementName=Control, Path=Option.Pr, Mode=OneWay}" />
            <TextBox Text="{Binding ElementName=Control, Path=Option.KUl, Mode=TwoWay}">
                <TextBox.InputBindings>
                    <KeyBinding Key="Return"
                                Command="{Binding ElementName=Control, Path=CalculateCommand}"
                                CommandParameter="{Binding ElementName=Control, Path=CalculateCommandParameter}" />
                </TextBox.InputBindings>
            </TextBox>
        </StackPanel>
    </Grid>
</UserControl>

und würde so verwendet

<uc:OptionsRow CalculateCommand="{Binding CalculateOptionCommand}"
               CalculateCommandParameter="{Binding Option1.RowNumber}"
               Option="{Binding Option1}"
               Visibility="{Binding Option1.OptionsRowIsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" />

Hat die Blume einen Knick, war der Schmetterling zu dick.

T
TreaxP Themenstarter:in
11 Beiträge seit 2023
vor einem Jahr

Da ich Anfänger bin, ist leider noch gar nichts klar 😉

Was du meinst habe ich verstanden, bei der Umsetzung happert es aber leider schon den ganzen Nachmittag. Liegt es an der Bindung im MainWindow.xaml an CalculateOptionCommand?

MainWindow.xaml:

<uc:OptionsRow Grid.Row="0" Visibility="{Binding Option1.OptionsRowIsVisible, Converter={StaticResource booleanToVisibilityConverter}}" DataContext="{Binding Option1}"
                                                                              CalculateOptionCommand="{Binding RelativeSource={RelativeSource AncestorType=vm:MainViewModel}, Path=CalculateOptionCommand}"/>

OptionsRow.xaml (TextBox im UserControl):

<TextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Foreground="Black" Text="{Binding KUl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
           <TextBox.InputBindings>
               <KeyBinding Key="Return" Command="{Binding CalculateOptionCommand}" CommandParameter="{Binding RowNumber, Mode=OneWay}"/>
           </TextBox.InputBindings>         
       </TextBox>

OptionsRow.xaml.cs (Codebehind vom UserControl):


       private static readonly DependencyProperty RowNumberProperty = DependencyProperty.Register("RowNumber", typeof(int), typeof(OptionsRow), new PropertyMetadata(0));
       private static readonly DependencyProperty CmdProperty = DependencyProperty.Register("CalculateOptionCommand", typeof(RelayCommand), typeof(OptionsRow));   

       public int RowNumber
       {
           get { return (int)GetValue(RowNumberProperty); }
           set { SetValue(RowNumberProperty, value); }
       }
       public RelayCommand CalculateOptionCommand
       {
           get { return (RelayCommand)GetValue(CmdProperty); }
           set { SetValue(CmdProperty, value); }
       } 
126 Beiträge seit 2023
vor einem Jahr

So ein Control hat einen DataContext.

Wird dieser nicht explizit gesetzt, dann wird dieser vom Parent durchgereicht.

Du hast diesen ja explizit auf Option1 gesetzt und somit wird dieses

<KeyBinding Key="Return" Command="{Binding CalculateOptionCommand}" CommandParameter="{Binding RowNumber, Mode=OneWay}"/>

gegen diesen DataContext versucht aufzulösen.

Aber genau das willst du ja nicht, sondern du willst das ja mit den Eigenschaften vom Control binden. Das habe ich dir schon gezeigt, hier aber noch einmal reduziert auf die wesentliche Teile:

<UserControl ...
             x:Name="Control"
             ...>
    ...
    <KeyBinding Key="Return"
                Command="{Binding ElementName=Control, Path=CalculateOptionCommand}"
                CommandParameter="{Binding ElementName=Control, Path=RowNumber}" />
    ...
</UserControl>

Allerdings sehe ich nicht, wo du das Binding für RowNumber machst.

Hier auf jeden Fall nicht:

<uc:OptionsRow Grid.Row="0" 
               Visibility="{Binding Option1.OptionsRowIsVisible, Converter={StaticResource booleanToVisibilityConverter}}" 
               DataContext="{Binding Option1}"
               CalculateOptionCommand="{Binding RelativeSource={RelativeSource AncestorType=vm:MainViewModel}, Path=CalculateOptionCommand}"/>

Wenn du weiter Hilfe benötigst, dann solltest du ein reduziertes aber vollständiges Projekt hochladen, dann kann ich da einmal drüberschauen, entsprechende Änderungen vornehmen und dir zurückschicken.

Hat die Blume einen Knick, war der Schmetterling zu dick.

T
TreaxP Themenstarter:in
11 Beiträge seit 2023
vor einem Jahr

Freude herrscht! 
Das funktioniert mit deiner Variante bestens. Anscheinend hat sich dein weiterer Beitrag mit meinem überschnitten, sonst wäre es schon vorher klar gewesen. Vielen Dank für deine geduldige und sehr wertvolle Hilfe.

Wenn du mir noch eine letzte Frage erlaubst: Die TextBox aktualisiert den Wert erst (wie standardmässig vorgesehen) bei LostFocus. Also wenn ich den Focus wechsle, zur TextBox zurückkehre und dann "Return" drücke. Mit "PropertyChanged" geht das ohne den Fokuswechsel. Dann kann ich in die TextBox aber lediglich noch ganze Zahlen eingeben wie z.B. 45 und nicht etwa 45.15.

4.939 Beiträge seit 2008
vor einem Jahr

Solange du die CultureInfo(Application Culture / UICulture) nicht geändert hast, sollte diesem dem des Systems entsprechen (bei dir also wohl deutsch), d.h. du mußt ein Komma eingeben (45,15) - das sollte aber unabhängig vom Binding Modesein.

126 Beiträge seit 2023
vor einem Jahr

Zitat von TreaxP

Wenn du mir noch eine letzte Frage erlaubst:

Ich bin in keinerlei Position hier etwas zu verbieten geschweige denn etwas zu erlauben 😃

Zu deiner eigentlichen Frage:

Wenn du dafür eine vernünftige Lösung haben willst, dann solltest du auf ein spezielles Control zurückgreifen, was explizit für die Eingabe eines double oder decimal Werts gedacht ist.

Da wäre z.B. DecimalUpDown vom Extended.Wpf.Toolkit

Hat die Blume einen Knick, war der Schmetterling zu dick.

126 Beiträge seit 2023
vor einem Jahr

@Th69
Meinn System ist auch auf deutsch und Thread.CurrentThread.CurrentCulturesowie Thread.CurrentThread.CurrentCulturestehen auf de-DEund trotzdem muss ich in einer TextBox als Dezimal-Trenner ein .verwenden.

Um das zu ändern muss man beim Start der Anwendung folgendes aufrufen:

FrameworkElement.LanguageProperty.OverrideMetadata(
    typeof( FrameworkElement ),
    new FrameworkPropertyMetadata( XmlLanguage.GetLanguage( CultureInfo.CurrentCulture.IetfLanguageTag ) ) );

Nun klappt es auch mit dem ,.

Hat die Blume einen Knick, war der Schmetterling zu dick.

T
TreaxP Themenstarter:in
11 Beiträge seit 2023
vor einem Jahr

Das ganze kann noch viel einfacher gelöst werden und funktioniert (StringFormat und UpdateSourceTrigger):

<TextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Foreground="Black" Text="{Binding ElementName=userControlOptionsRow, Path=Option.KUl, Mode=TwoWay,StringFormat={}{##.##},UpdateSourceTrigger=PropertyChanged}">
           <TextBox.InputBindings>
               <KeyBinding Key="Return"
                           Command="{Binding ElementName=userControlOptionsRow, Path=CalculateCommand}" 
                           CommandParameter="{Binding ElementName=userControlOptionsRow, Path=CalculateCommandParameter}"/>
           </TextBox.InputBindings>
</TextBox>   
4.939 Beiträge seit 2008
vor einem Jahr

Das ist ja komplett "bescheiden", daß man dies zusätzlich explizit setzen muß.

Und es scheint sogar noch einen Unterschied zu geben, s. Kommentare in #765 – WPF Data Binding Ignores CurrentCulture, also wenn man Änderungen am eigenen System vorgenommen hat, wird trotzdem ersteinmal nur die Standard-Einstellung übernommen (außer man benutzt ConverterCulture: Localized Value Formatting in WPF).

126 Beiträge seit 2023
vor einem Jahr

@TreaxP

Komplett rund ist die Lösung aber nicht, denn mit dem StringFormat wird kein Wert dargestellt, das Feld bleibt leer. Taugt also nur für ein reines Eingabefeld.

Wenn das reicht, dann ist es eine ausreichende Lösung. Bei mehr würde ich ein passendes Control empfehlen.

Hat die Blume einen Knick, war der Schmetterling zu dick.

T
TreaxP Themenstarter:in
11 Beiträge seit 2023
vor einem Jahr

Ja das ist auch wieder wahr. Daher habe ich ein passendes Control von Extended.WPF.Toolkit implementiert, wobei ich nun aber wieder beim alten Problem angelangt bin, dass "LostFocus" zwar funktioniert aber das Drücken der "Return-Taste" nicht.

<xctk:AutoSelectTextBox Style="{StaticResource styleRowTextBox}" Grid.Column="3" Margin="2 4" Foreground="Black" Text="{Binding ElementName=userControlOptionsRow, Path=Option.KUl, Mode=TwoWay}" AutoSelectBehavior="OnFocus">
           <i:Interaction.Triggers>
              <i:EventTrigger EventName="LostFocus">
                  <i:InvokeCommandAction Command="{Binding ElementName=userControlOptionsRow, Path=CalculateCommand}"
                                         CommandParameter="{Binding ElementName=userControlOptionsRow, Path=CalculateCommandParameter}"/>
              </i:EventTrigger>
          </i:Interaction.Triggers>  
</xctk:AutoSelectTextBox>

Ist denn das normal, dass alles so zusammengebastelt werden muss? Ich möchte ja nur eine einfach TextBox in einem UserControl, dessen dahinterliegende Eigenschaft angepasst wird, wenn ich die Return-Taste drücke. Habe ich (noch) komplett etwas nicht verstanden oder ist das wirklich so mühsam? Bzw. woher weiss ich für was ich was einsetzen muss und wo die Grenzen von den einzelnen Elementen/Controls liegen usw....

126 Beiträge seit 2023
vor einem Jahr

Ich weiß ja nicht was du so vorhast, aber ich hätte das so gelöst (was auch funktioniert) und macht genau das, was du bisher so beschreiben hast. Man tippt einen beliebigen Wert und beim Drücken der Return-Taste wird ein Command aufgerufen und der eingegebene Wert steht im ViewModel zur Verfügung.

<xctk:DecimalUpDown Value="{Binding ValueTwo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <xctk:DecimalUpDown.InputBindings>
        <KeyBinding Key="Return"
                    Command="{Binding DoSomething}"
                    CommandParameter="{Binding ValueTwo}" />
    </xctk:DecimalUpDown.InputBindings>
</xctk:DecimalUpDown>

Hat die Blume einen Knick, war der Schmetterling zu dick.

T
TreaxP Themenstarter:in
11 Beiträge seit 2023
vor einem Jahr

Nun habe ich den DecimalUpDown genommen und einfach die SpinnerButtons entfernt. Das Resulat: Es funktioniert und sieht wie eine normale TextBox aus. Fantastisch.

P
257 Beiträge seit 2010
vor 10 Monaten
UserControl und DataContext und ViewModel

Hallo!

Ich knüpfe hier mal an den alten Beitrag an, da ich adäquat eine generelle Frage habe.

In dem ganzen Beitrag ist nirgendwo ein ViewModel zu sehen, obwohl die Überschrift dieses suggeriert.  😃

Meine Frage zielt aber genau darauf ab.

Ich habe (letztendlich), so wie Blonder Hans es auch handhabt, die Eigenschaften eines UserControl's über DP's öffentlich zugängig gemacht. Das Binding innerhalb des UC's erfolgt entsprechend über den (Element)Namen des UC's und dem Namen der DP.

Diese Vorgehensweise beinhaltet erst einmal gar kein ViewModel. Änderungen an der DP aktualisieren die UC-View.

Jede Eigenschaft die die UC-View aktualisieren soll bedarf dadurch aber einer DP. Die DP kann auch private deklariert werden, so dass sie letztendlich öffentlich nicht mehr sichtbar ist. Alle Logik wird jetzt in der CodeBehind abgebildet.

Meine Frage ist nun, ist das der Standard und ist dass überhaupt noch MVVM?

Das ist jetzt nicht nur so eine allgemeine Frage.

Ich drücke es mal anders aus: Kann man ein UserControl auch wie eine MVVM Anwendung mit View und ViewModel als DataContext und Bindings auf das ViewModel aufbauen? Ich habe dann bei der Implementierung des UC's in der Anwendung immer das Problem, dass Eigenschaften des ViewModel's der Anwendung nicht im UC gebunden werden können, da die UC die Eigenschaft immer in ihrem VM sucht.

2.079 Beiträge seit 2012
vor 10 Monaten

In dem ganzen Beitrag ist nirgendwo ein ViewModel zu sehen, obwohl die Überschrift dieses suggeriert.

Man kann auch Bindings haben, ohne das ViewModel zu zeigen 😉
Die konkrete Implementierung des ViewModels ist unter Umständen gar nicht relevant, ist am Ende nur eine Property, meist mit irgendeinem MVVM-Framework implementiert. Es reicht zu wissen, wie die Property heißt und das sieht man am Binding.

Meine Frage ist nun, ist das der Standard und ist dass überhaupt noch MVVM?

Nein, ist kein MVVM.

MVVM unterscheidet (ich wähle bewusst etwas andere Bezeichnungen) View-Logik (View) und View-Business-Logik (ViewModel).
Alles, was Business-Logik ist, sollte prinzipiell gar nicht in der View auftauchen, aber das geht natürlich nicht überall und der Rest landet im ViewModel.
Die reine View-Logik, die unabhängig von der Business-Logik ist, die darf im XAML oder CodeBehind oder ähnlichen View-Technologien stehen.

Ein Beispiel für View-Logik wäre z.B. eine Drag&Drop-Sortierung einer Liste. Im ViewModel ist dann die ObservableCollection und im CodeBehind (oder einer anderen View-Technologie) wird das Drag&Drop behandelt, die die Items in der View neu anordnet und dadurch (indirekt) auch das ViewModel aktualisiert.

Die View-Logik darf natürlich auch im ViewModel stattfinden, allerdings gibt es meines Wissens nach keine gute Abstraktion um Drag&Drop, die brauchst Du aber, denn die View selber, also Controls und View spezifische EventArgs und so weiter, haben im ViewModel nichts verloren.

Bedenke immer:
Das ViewModel soll ohne View funktionsfähig sein.
Das funktioniert aber nur, wenn das ViewModel unabhängig oder nur von Abstraktionen abhängig ist.

die Eigenschaften eines UserControl's über DP's öffentlich zugängig gemacht. Das Binding innerhalb des UC's erfolgt entsprechend über den (Element)Namen des UC's und dem Namen der DP.

DendencyProperties sind dafür da, Controls auszulagern und mit eigenen Dingen einstellbar zu machen. Das kann z.B. dafür sein, ein Control an mehreren Stellen mit unterschiedlichen ViewModels nutzbar zu machen, oder Du möchtest einen Teil einer View auslagern, aber aus irgendwelchen Gründen (z.B. Übersicht) keine ViewModel-Bindings in dem Control haben, dann kannst Du das so machen. Ich persönlich nutze Dependency Properties aber fast nur als Attached Property, oder in einem Custom Control mit einem ausgelagerten Template, da kann man dann schön mit TemplateBindings arbeiten.

Ein Control als ViewModel zu missbrauchen, ist aber ganz sicher keine gute Idee.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

P
257 Beiträge seit 2010
vor 10 Monaten

Hallo Palladin007!

Da fehlen mir leider noch viele Zusammenhänge!

Ich wollte das UserControl genauso wie jede andere Applikation (MVVM gerecht) aufbauen. Geht auch alles. Die View ist das UC, eine Klasse für das ViewModel, Bindings in der View an die Eigenschaften / Commands des ViewModels über den DataContext der im Root des UC instanziert wird. Alles prima.

Ein Problem tritt eigentlich nur bei Bindings der instanzierten UC's an VM-Eigenschaften der Apps auf.

<tbac:UCTextBoxAutoComplete Text="{Binding BeispielText}" />

BeispielText ist eine VM-Eigenschaft des VM der App.

Im UC binde ich die Text-Eigenschaft über den Namen an das VM der UC:

<UserControl x:Name="ucTBAC" x:Class="ARControls.TextBoxAutoComplete.UCTextBoxAutoComplete"
   ...
   d:DesignHeight="80" d:DesignWidth="150">
    <UserControl.DataContext>
        <local:VM x:Name="ucvm"/>
    </UserControl.DataContext>

    ...
       <TextBox Text="{Binding Path=Text, ElementName=ucvm}"/>

    ...
</UserControl>

Dieses Binding kann aber nicht ausgeführt werden, da die instanziierte UC in der App die Eigenschaft in ihrem VM sucht, obwohl ich die Eigenschaft Text an BeispielText binde.

Error: Die Eigenschaft "BeispielText" wurde im Objekt vom Typ "VM" nicht gefunden.

Vielen Dank! für deine Erklärung(en)!

126 Beiträge seit 2023
vor 10 Monaten

Stell dir einmal vor so ein Control wie die TextBox wäre so konzipiert wie dein UserControl - dann könntest du dieses Binding

<TextBox Text="{Binding FirstName}"/>

nicht einfach so machen, sondern du müsstest im CodeBehind auf den DataContext dieser TextBox zugreifen und dort der Eigenschaft Text den entsprechenden Wert zuweisen. Sehr gruselig.

Gottlob wurde das nicht so gemcht und es gibt eben diese DependencyProperty.

Hat die Blume einen Knick, war der Schmetterling zu dick.

P
257 Beiträge seit 2010
vor 10 Monaten

Guten Morgen Blonder Hans!

Na OK, dann erstelle ich eben 20-30 DP's, wenn das der richtige Weg ist.

Vielen Dank in später Stunde!

2.079 Beiträge seit 2012
vor 10 Monaten

Das sieht komisch aus ...
Du bindest den Text beim Aufruf des Controls an irgendein ViewModel.
Und in dem Control bindest Du den tatsächlichen Text wieder an irgendein ViewModel, diesmal hart im XAML definiert.
Eines der beiden Bindings ist Quatsch 😉 So wie Du das aufgebaut hast, darf dein UserControl kein ViewModel sehen.

"Richtiger" wäre, Du definierst eine Dependency Property, aber ein UserControl scheint für mich generell nicht der richtige Weg.

Ich würde zwei Wege in Betracht ziehen:

Nimm eine TextBox und arbeite damit, gerne auch im CodeBehind (solange die Vorschlagsdaten vom ViewModel kommen), das Auto Complete ist dann nur Logik, um auf Textänderungen zu reagieren und die Vorschau zu aktualisieren. Du könntest die Vorschau auch als Adorner implementieren, das wäre weniger fehleranfällig. Und die ganze Logik würde ich als Behavior implementieren, dann kannst Du es an verschiedenen Stellen nutzen, ohne ein eigenes Control zu haben. Geht auch als Attached Property, aber für komplexere Logik finde ich Behaviors bedeutend einfacher.

Oder Du erstellst doch ein eigenes Control, aber dann kein UserControl, sondern ein CustomControl.
Einfach eine normale Klasse für dein Control erstellen, die von TextBox oder TextBoxBase (musst Du schauen, was besser ist) ableitet.
Dort definierst Du dann deine ganze Logik und alle *zusätzlichen* Dependency Properties, die du brauchst, sowas wie Text erbst Du ja von der Basis.
Und daneben (oder wo auch immer) erstellst Du ein ResourceDictionary mit einem Style für dieses neue Control (ggf. kannst Du den TextBox-Style als Base nutzen). Der Style definiert dann z.B. auch ein Template, wo Du dann deine View für die AutoCompleteTextBox definieren kannst. Dort kannst Du dann mit TemplateBindings auf die DependencyProperties zugreifen.

Soweit ich das aktuell sehe, würde ich zum Behavior tendieren. Wenn Du den TextBox-Style als Style-Base nutzen kannst, wäre das CustomControl aber wiederum interessanter.

PS:

Nach dem Bild in deiner anderen Frage: Das ist definitiv ein Adorner 😃

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

P
257 Beiträge seit 2010
vor 10 Monaten

Hallo Palladin007!

Eine Fülle an Möglichkeiten!!! <schwitz>

  • Du hast natürlich Recht: So wie Du das aufgebaut hast, darf dein UserControl kein ViewModel sehen. 
    Mit schlechtem Gewissen geht dann so etwas: (Im UC die zu bindenden Eigenschaften über den DataContext vom VM der App "holen")
<tbac:UCTextBoxAutoComplete Grid.Row="0" Grid.Column="2" 
    Text="{Binding DataContext.BeispielText, RelativeSource={RelativeSource AncestorType=local:MainWindow}}" 
    Liste="{Binding DataContext.BeispielListe, RelativeSource={RelativeSource AncestorType=local:MainWindow}}" />

Ich habe aber das UC auf DP's umgestellt und arbeite da ohne ViewModel.

  • Auch im UC arbeite ich schon immer mit einem (TextBox-TextChanged) Behavior. Im Behavior hole ich mir (jetzt) das UC eben über den VisualTree und werde die Logik, um auf Textänderungen zu reagieren, implementieren.
public class TextBoxTextChangedBehavior : Behavior<TextBox>
{
    protected override void OnAttached() => AssociatedObject.TextChanged += AssociatedObject_TextChanged;
    protected override void OnDetaching() => AssociatedObject.TextChanged -= AssociatedObject_TextChanged;

    private void AssociatedObject_TextChanged(object sender, RoutedEventArgs e)
    {
        TextBox tb = (TextBox)sender;               // TextBox "holen"
        UCTextBoxAutoComplete UC = arWPF.WPF_Tools.WPF_Tools.VisualTreeHelpers.FindAncestor<UCTextBoxAutoComplete>(tb); // UC "holen"                                
...

    }
}
  • Die Vorgehensweise zur Lösung über ein CustomControl finde ich auch sehr spannend und ordentlich. Werde mich damit auseinandersetzen. Vielen Dank für deine detalierte Beschreibung der Umsetzung!
2.079 Beiträge seit 2012
vor 10 Monaten

Mir ist nicht ganz klar, was Du in deiner Antwort sagen willst, besonders Verwirrend finde ich, wie Du die Aufzählungen verwendest. Willst Du ggf. Zitieren?

Der XAML-Code mit dem DataContext-Binding über das MainWindow ist aber gruselig, das würde niemals ein Review bei mir überstehen.
Ich hoffe (wegen deiner "Mit schlechtem Gewissen" Formulierung) ist klar, wieso? 😄

Und das Behavior:
So wie ich das meine, brauchst Du kein UCTextBoxAutoComplete mehr.
Du hast dann nur noch die TextBox und das Behavior, im Behavior wird auf Text-Änderungen gehört und zeigst ein eigenes Vorschau-Control als Adorner an.
Du kannst natürlich trotzdem ein eigenes Control bauen, aber dann würde ich das als CustomControl und ohne Adorner machen, die TextBox ist dann im Template definiert und Du kannst sie als TemplatePart abrufen.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

P
257 Beiträge seit 2010
vor 10 Monaten

Hallo Paladin007!

Ja, Zitieren wäre wohl besser gewesen! Weil wir gerade bei der Oberfläche sind, ich vermisse noch die alte Error-"Box" (analog der Code oder XAML Box).

Da ich die AutoComplete-TextBox allein in diesem Projekt schon sehr oft benötige wäre es sicher sinnvoll ein eigenes CustomControl zu erstellen. Das es auch ohne geht, habe ich aus deiner Antwort schon herausgelesen. Damit muss ich mich aber erst einmal beschäftigen. Zur Zeit drängelt die Umsetzung der Logik erst noch.

... die TextBox ist dann im Template definiert und Du kannst sie als TemplatePart abrufen

Wird mir sicher bald etwas sagen! 😃

Vielen Dank!

PS: Wie gebe ich hier an, von wem dieses Zitat ist?

16.834 Beiträge seit 2008
vor 10 Monaten

Weil wir gerade bei der Oberfläche sind, ich vermisse noch die alte Error-"Box" (analog der Code oder XAML Box).

Markdown kennt leider kein solches Feature, das wir dafür verwenden könnten. Daher verwende entsprechend zB Quote oder einen Code-Block (zB mit Kommando-Zeilen-Formatierung).

2.079 Beiträge seit 2012
vor 10 Monaten

Zitat von perlfred

Wie gebe ich hier an, von wem dieses Zitat ist?

Das musst Du leider manuell machen, bzw. bei der "Zitieren" Funktion wird das automatisch gemacht.
Einfach "Zitat von XY" davor schreiben.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.