Laden...

ListView.SelectedItem an UserControl binden

Erstellt von kelo vor einem Jahr Letzter Beitrag vor einem Jahr 593 Views
K
kelo Themenstarter:in
4 Beiträge seit 2023
vor einem Jahr
ListView.SelectedItem an UserControl binden

Hallo zusammen,
ich würde gerne in einer Page mit einer ListView und einem ContentControl die SelectedItems der ListView in einem von mir erstellten UserControl anzeigen und bearbeiten.
Dazu möchte ich per DataTemplates die zum Typen zugehörigen UserControls laden.

HistoryPage.xaml


<Page ...HistoryPage
      d:DataContext="{d:DesignInstance HistoryPage, IsDesignTimeCreatable=False}"
    <Page.Resources>
        <ResourceDictionary>
            <DataTemplate DataType="{x:Type MyType1}">
                <MyUserControl1/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type MyType2}">
                <MyUserControl2/>
            </DataTemplate>
        </ResourceDictionary>
    </Page.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="9*"></RowDefinition>
            <RowDefinition Height="1*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="6*"></ColumnDefinition>
                <ColumnDefinition Width="4*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <ListView ItemsSource="{Binding ViewModel.Actions}"
                      ItemTemplateSelector="{StaticResource MyDataTemplateSelector}"
                      SelectedItem="{Binding ViewModel.SelectedAction}">
            </ListView>
            <ContentControl Grid.Column="1" Content="{Binding Path=ViewModel.SelectedAction}"/>
        </Grid>
        ...
    </Grid>
</Page>

HistoryPage.xaml.cs


 public partial class HistoryPage : INavigableView<HistoryViewModel>
    {
        public HistoryViewModel ViewModel {
            get;
        }

        public HistoryPage(HistoryViewModel viewModel)
        {
            ViewModel = viewModel;
            DataContext = this;

            InitializeComponent();
        }

    }

HistoryViewModel.cs


public partial class HistoryViewModel : ObservableObject, INavigationAware
{
    private bool _isInitialized = false;

    
    [ObservableProperty] private ObservableCollection<IAction> _actions = new();
    [ObservableProperty] private IAction _selectedAction;

    public HistoryViewModel()
    {
    }
    ...
}

So schaffe ich es schonmal das zugehörige UserControl zu laden. Bearbeiten kann ich noch nichts, da ich relativ neu in WPF und dem "neuen C#" bin scheine ich etwas zu übersehen. Hier ist mein Usercontrol zum bearbeiten eines Types:

MyUserControl1.xaml


<UserControl ...MyUserControl1
             ...
             d:DataContext="{d:DesignInstance m:MyUserControl1, IsDesignTimeCreatable=False}"
             ...
              >
    <StackPanel>
        <local:LabeledInputUserControl Description="Id" Input="{Binding Id, UpdateSourceTrigger=PropertyChanged}"/>
        <local:LabeledInputUserControl Description="Timestamp" Input="{Binding Timestamp}"/>
        <local:LabeledInputUserControl Description="Value" Input="{Binding Amount}"/>
    </StackPanel>
</UserControl>


 public partial class MyUserControl1 : UserControl
    {
        public TransactionUserControl()
        {
            InitializeComponent();
            DataContext = this;
        }
    }

Und hier mein UserControl zur Eingabe von Werten:

LabeledInputUserControl.xaml


<UserControl ... LabeledInputUserControl
             d:DataContext="{d:DesignInstance local:LabeledInputUserControl, IsDesignTimeCreatable=False}"
             ....>
    <WrapPanel HorizontalAlignment="Stretch" Width="300" Height="Auto">
        <Label Content="{Binding Description, FallbackValue=Description, TargetNullValue=Description}"
               Width="120"
               Height="Auto"
               Style="{StaticResource TextBoxLabel}"/>
        <TextBox Text="{Binding Input}" Width="180" Height="Auto"/>
    </WrapPanel>
</UserControl>

LabeledInputUserControl.xaml.cs


public partial class LabeledInputUserControl
{
    public string Description { get; set; }

    public static DependencyProperty InputProperty = DependencyProperty.Register(nameof(Input), typeof(string), typeof(LabeledInputUserControl), new PropertyMetadata());

    public string Input
    {
        get => (string)GetValue(InputProperty);
        set => SetValue(InputProperty, value);
    }


    public LabeledInputUserControl()
    {
        InitializeComponent();
        DataContext = this;
    }
}

Wie kann ich es so binden, dass ich das wie oben beschrieben bewerkstelligen kann?
Liebe Grüße

A
764 Beiträge seit 2007
vor einem Jahr

Hallo kelo

Wo genau hängt es?
Gibt es eine Fehlerausgabe im Output?
Wie sieht denn der

MyDataTemplateSelector

aus?

Gruss Alf

K
kelo Themenstarter:in
4 Beiträge seit 2023
vor einem Jahr

Hallo Alf,
danke für deine Rückfrage. Ich würde gerne meinen ersten Beitrag bearbeiten, finde aber keine Option dazu. Den DataTemplateSelector kann man ignorieren, er ist bezüglich der Fragestellung unrelevant.
Hier nochmal die aktualisierten Klassen von mir


<Page ...HistoryPage
      d:DataContext="{d:DesignInstance HistoryPage, IsDesignTimeCreatable=False}"
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="9*"></RowDefinition>
            <RowDefinition Height="1*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="6*"></ColumnDefinition>
                <ColumnDefinition Width="4*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <ListView ItemsSource="{Binding ViewModel.Actions, Mode=TwoWay}"
                      SelectedItem="{Binding ViewModel.SelectedAction, Mode=TwoWay}">
            </ListView>
            <MyUserControl1 Grid.Column="1" Action="{Binding ViewModel.SelectedAction, Mode=TwoWay}"/>
        </Grid>
        ...
    </Grid>
</Page>

HistoryViewModel.cs


public partial class HistoryViewModel : ObservableObject, INavigationAware
{
    ....
    public ObservableCollection<IAction> Actions { get; set; } = new();
    [ObservableProperty] private IAction _selectedAction;

    public HistoryViewModel()
    {
    }
    ...
}

Action.cs


public record Action() : IAction
{
    public string Id { get; set; } = Constants.NotAvailable;

    public long Timestamp { get; set; } = 0L;

   public string Value { get; set; } = Constants.NotAvailable;
}

Und hier die aktualisierte Fassung meiner MyUserControl1.xaml


<UserControl ...MyUserControl1
             ...
             d:DataContext="{d:DesignInstance m:MyUserControl1, IsDesignTimeCreatable=False}"
             ...
              >
    <StackPanel DataContext="{Binding Path=Action, RelativeSource={RelativeSource AncestorType=UserControl}}">
        <local:LabeledInputUserControl Description="Id" Input="{Binding Id, UpdateSourceTrigger=PropertyChanged}"/>
        <local:LabeledInputUserControl Description="Timestamp" Input="{Binding Timestamp}"/>
        <local:LabeledInputUserControl Description="Value" Input="{Binding Value}"/>
    </StackPanel>
</UserControl>

MyUserControl1.xaml.cs


 public partial class MyUserControl1: UserControl
    {
        public static DependencyProperty ActionProperty = DependencyProperty.Register(nameof(Action), typeof(Action), typeof(MyUserControl1), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

        public Action Action
        {
            get => (Action)GetValue(ActionProperty);
            set => SetValue(ActionProperty , value);
        }

        public MyUserControl1()
        {
            InitializeComponent();
        }
    }

Wenn ich ein Item auswähle werden die Properties im UserControl angezeigt, sobald ich dort Werte abändere ist es mir nicht mehr möglich ein anderes Item auszuwählen, sprich das SelectedItem wird nicht mehr geändert. Wenn ich nichts ändere funktioniert es. Ich habe schon verschiedene UpdateSourceTrigger ausprobiert, komme da aber nicht weiter. Ich hoffe so ist es für dich ersichtlicher.
Liebe Grüße

A
764 Beiträge seit 2007
vor einem Jahr

Ich glaube deine Action-Properties müssen noch mit der UI über Änderungen kommunizieren.
Das heisst INotifyPropertyChanged für Action implementieren, oder ObservableObject.
Kann sein, dass du aus dem record dann ein class machen musst. Weiss ich nicht genau.


public record Action() : ObservableObject, IAction
{
    [ObservableProperty]
    public string Id { get; set; } = Constants.NotAvailable;

    [ObservableProperty]
    public long Timestamp { get; set; } = 0L;

    [ObservableProperty]
    public string Value { get; set; } = Constants.NotAvailable;
}

Gruss
Alf

K
kelo Themenstarter:in
4 Beiträge seit 2023
vor einem Jahr

Hallo Alf,
danke für deine Antwort. Ich habe nach weiteren Überlegungen deinen Ratschlag befolgt und es umgesetzt. Du hattest Recht, so funktioniert es. Allerdings dachte ich man würde solche "ViewLogik" nicht in die Models packen. Also habe ich noch etwas rumprobiert und einfach nur mal den record in eine Klasse umgewandelt. Und siehe da, es funktioniert, ohne ObservableObject as parent. Wie kann ich mir das erklären?

public record Action() :  IAction
{
    public string Id { get; set; } = Constants.NotAvailable;

    public long Timestamp { get; set; } = 0L;

    public string Value { get; set; } = Constants.NotAvailable;
}

16.835 Beiträge seit 2008
vor einem Jahr

einfach nur mal den record in eine Klasse umgewandelt.

Beides sind Klassen. Records sind nur eine Kurzschreibweise für eine gewisse Klassendarstellung.
Ein Seiteneffekt wird sein, dass WPF records nicht voll unterstützt. Es empfiehlt sich die klassische Schreibweise zu verwenden, in Deinem Fall


public class Action :  IAction
{
    public string Id { get; set; } = Constants.NotAvailable;

    public long Timestamp { get; set; } = 0L;

    public string Value { get; set; } = Constants.NotAvailable;
}

Eine Record-Deklaration in Deiner letzten Darstellung macht eh so kein Sinn mehr.

Was ein Record ist:


public record class MyClass(string MyString);

ist das gleiche wie


public record MyClass(string MyString);

ist das gleiche wie


public class MyClass
{
   public required string MyString { get; init; }
};

Und WPF mag das init nicht.

Dein Constants.NotAvailable; ist übrigens so nen relativ übler Code Smell 😉

K
kelo Themenstarter:in
4 Beiträge seit 2023
vor einem Jahr

Hallo Abt, danke für deine Antwort. Jetzt werde ich weiterkommen 🙂

Dein Constants.NotAvailable; ist übrigens so nen relativ übler Code Smell 😉

Da wirst du recht haben, ich gelobe Besserung.

Liebe Grüße