当前位置:   article > 正文

[Wpf] 前端元素变化但后端数据没变的解决方法_wpf xaml页面中按钮的content值变了,vm中怎么没跟着变

wpf xaml页面中按钮的content值变了,vm中怎么没跟着变

自己随便瞎写写遇到的离谱问题,还是写一下以免自己下一次又手足无措
ps: gpt真是个好东西

问题描述

一个普普通通的界面
在这里插入图片描述
点击眼睛图标会将其中的图像展示出来,默认为Undefined.jpg

在这里插入图片描述
装数据的元素如下:

<ScrollViewer Style="{StaticResource MaterialDesignScrollViewer}" VerticalScrollBarVisibility="Auto">
  <ListBox
    x:Name="SideImage"
    ItemContainerStyle="{StaticResource DefaultListBoxItem}"
    ItemsSource="{Binding Images}">
    <i:Interaction.Triggers>
      <i:EventTrigger EventName="PreviewMouseRightButtonUp">
        <i:InvokeCommandAction Command="{Binding RemoveCommand}" CommandParameter="{Binding ElementName=SideImage, Path=SelectedItem}" />
      </i:EventTrigger>
    </i:Interaction.Triggers>
    <ListBox.ItemTemplate>
      <DataTemplate>
        <DockPanel>
          <TextBlock MinWidth="10" Style="{StaticResource DefaultText}">
            <Run Text="{Binding Index}" />
          </TextBlock>
          <controls:ViewImage />
        </DockPanel>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</ScrollViewer>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

ItemSource中的Images的定义为:

        public ObservableCollection<ViewImageViewModel> Images { get => images; set => SetProperty(ref images, value); }
  • 1

然后,在ViewImageViewModel中定义了一个用来拖放图片的方法,如下所示:

private void Drop(DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
        string file = files[0];
        ImageUri = DefaultImage.GetImage(new(file));
        Debug.WriteLine(file);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

向其中拖放图片会得到如下效果:
图片是可以正常插入和显示的
然后就是问题所在了,去后台检查数据会发现Images中的数据并没有被修改。
在这里插入图片描述
可以看到ImageUri并没有修改,仍然维持着默认值Undefined.jpg
Description也没有发生改变。

问题分析

根据gpt的分析

在你的拖放方法Drop中,你修改了ImageUri属性,但是没有更新Images集合。
因此,虽然界面上的图片被修改了,但是Images集合中对应的ViewImageViewModel对象并没有被更新。
  • 1
  • 2

感觉分析的很有道理,但我不解的是ListBox的数据上下文是Images,照理说他们应该指向同一片地址(以我浅薄的C语言知识来说)。但是并没有,前端改了,后台的数据有没变,那前端绑定的是哪儿的数据?

然后还是那个gpt,他告诉我

对的,当你绑定一个集合到 ListBox 的 ItemsSource 属性时,WPF 会遍历集合中的每个元素,并为每个元素创建一个对应的 UI 元素实例。

这意味着,如果你的集合有 5 个元素,那么 WPF 会为每个元素创建一个 UI 元素实例,然后将这些 UI 元素放入 ListBox 中。这些 UI 元素的显示方式由 ListBox 的 ItemTemplate 决定。
  • 1
  • 2
  • 3

也就是说,对于集合元素,wpf是会为每一个元素创建一个对应的UI实例,和咱绑定的Images的关系只有最开始的初始化!!!

为了验证我的猜想,我又写了个测试用的窗口:

在这里插入图片描述
拖入元素之后如下:
在这里插入图片描述
图片被正确处理了(图像不全是布局问题),然后Debug按钮对应的指令为

DebugCommand = new(() => { Debug.WriteLine(Test.ImageUri); });
  • 1

可以的,结果出来了
在这里插入图片描述
ImageUri成功修改了!!!
让我们再看一眼Images
在这里插入图片描述
在这里插入图片描述
呵,还是那个Undefined,这样就能解释为什么前后不统一了。

解决问题

如果我分析的没错的话,现在就是看怎么建立绑定了。

第一想法是这样
ItemsSource="{Binding Images,Mode=TwoWay}">
事实证明啥用没有

………………想不出一点方法,毁灭吧


二次编辑

这是WPF的问题吗……用ItemsSource传递参数给DataContext一点用都没有……换成有限的就可以了……

<ListBox
  x:Name="SideImage"
  ItemContainerStyle="{StaticResource DefaultListBoxItem}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="PreviewMouseRightButtonUp">
      <i:InvokeCommandAction Command="{Binding RemoveCommand}" CommandParameter="{Binding ElementName=SideImage, Path=SelectedItem}" />
    </i:EventTrigger>
  </i:Interaction.Triggers>
  <ListBoxItem>
    <controls:ViewImage DataContext="{Binding Images[0]}" />
  </ListBoxItem>
  <ListBoxItem>
    <controls:ViewImage DataContext="{Binding Images[1]}" />
  </ListBoxItem>
  <ListBoxItem>
    <controls:ViewImage DataContext="{Binding Images[2]}" />
  </ListBoxItem>
  <ListBoxItem>
    <controls:ViewImage DataContext="{Binding Images[3]}" />
  </ListBoxItem>
</ListBox>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

我感觉不可能出这种问题,有大佬能告诉我该怎么用ItemsControl传参给DataContext吗……

一个邪道解法(perhaps)

过了这么多天,想了一个有用的解法,算是解决问题,但总感觉不像MVVM设计模式了,就是自己定义一个控件,用依赖属性解决问题

用来代替ViewImage的自定义控件DropableImage

<UserControl
  x:Class="Controller.Views.Controls.DropableImage"
  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:i="http://schemas.microsoft.com/xaml/behaviors"
  xmlns:local="clr-namespace:Controller.Views.Controls"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
  d:DesignHeight="250"
  d:DesignWidth="400"
  mc:Ignorable="d">
  <Border
    Height="{Binding ElementName=Presenter, Path=Height}"
    BorderBrush="{DynamicResource LighterGray}"
    BorderThickness="3,0,3,3">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition Width="auto" />
      </Grid.ColumnDefinitions>
      <Border Grid.ColumnSpan="2" Background="{DynamicResource LighterGray}" />
      <TextBlock Style="{StaticResource DefaultText}" Text="{Binding RelativeSource={RelativeSource AncestorType=local:DropableImage}, Path=Description}" />
      <StackPanel
        Grid.Column="1"
        HorizontalAlignment="Right"
        Orientation="Horizontal">
        <StackPanel.Resources>
          <Style BasedOn="{StaticResource FuncBtn}" TargetType="Button" />
        </StackPanel.Resources>
        <Button Click="VisibilityChanged">
          <md:PackIcon x:Name="EyeIcon" Kind="Eye" />
        </Button>
        <Button Content="{md:PackIcon Kind=ContentSave}" />
        <Button Click="RemoveCommand" Content="{md:PackIcon Kind=Close}" />
      </StackPanel>

      <Image
        x:Name="Presenter"
        Grid.Row="1"
        Grid.ColumnSpan="2"
        ClipToBounds="True"
        Source="{Binding RelativeSource={RelativeSource AncestorType=local:DropableImage}, Path=ImageUri, Mode=TwoWay}"
        Visibility="Visible" />
    </Grid>

  </Border>
</UserControl>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

控件截图
基本和ViewImage一个模子出来的
在xaml.cs里的代码如下

using MaterialDesignThemes.Wpf;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Controller.Views.Controls
{
    /// <summary>
    /// DropableImage.xaml 的交互逻辑
    /// </summary>
    public partial class DropableImage : UserControl
    {
        public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register("ImageUri",typeof(ImageSource), typeof(DropableImage));
        public static readonly RoutedEvent RemoveEvent = EventManager.RegisterRoutedEvent("Remove", RoutingStrategy.Bubble, typeof(RoutedEvent), typeof(DropableImage));
        public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(DropableImage),new("Rest in Peace"));
        public static readonly DependencyProperty OnRemoveProperty = DependencyProperty.Register("OnRemove", typeof(bool), typeof(DropableImage), new PropertyMetadata(false));

        public ImageSource ImageUri { get => (ImageSource)GetValue(ImageUriProperty); set => SetValue(ImageUriProperty, value); }
        public string Description { get => (string)GetValue(DescriptionProperty); set => SetValue(DescriptionProperty, value); }
        public bool OnRemove { get => (bool)GetValue(OnRemoveProperty);set=>SetValue(OnRemoveProperty, value); }

        public event RoutedEventHandler Remove
        {
            add { AddHandler(RemoveEvent, value); }
            remove { RemoveHandler(RemoveEvent, value); }
        }


        public DropableImage()
        {
            InitializeComponent();
        }

        private void VisibilityChanged(object sender, RoutedEventArgs e)
        {
            if(EyeIcon.Kind==PackIconKind.Eye) 
            {
                EyeIcon.Kind=PackIconKind.EyeClosed;
                Presenter.Visibility=Visibility.Collapsed;
            }
            else
            {
                EyeIcon.Kind=PackIconKind.Eye;
                Presenter.Visibility = Visibility.Visible;
            }
        }

        private void RemoveCommand(object sender, RoutedEventArgs e)
        {
            RoutedEventArgs e2 = new()
            {
                RoutedEvent = RemoveEvent
            };
            RaiseEvent(e2);

        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

(mvvm设计模式允许我在xaml.cs里写代码吗……)
主要就是依赖属性解决的问题,通过Binding绑定特定的数据,这样就不需要绑定DataContext,这样至少数据变动用反应了

之前的ListBox变成这样了

<ListBox
  x:Name="SideImage"
  AllowDrop="True"
  ItemContainerStyle="{StaticResource DefaultListBoxItem}"
  ItemsSource="{Binding BitmapSources}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="Drop">
      <i:InvokeCommandAction Command="{Binding DragEventCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
  </i:Interaction.Triggers>
  <ListBox.ItemTemplate>
    <DataTemplate>
      <controls:DropableImage x:Name="Item" Description="{Binding Title}" ImageUri="{Binding RelativeSource={RelativeSource Mode=PreviousData}, Path=ImageUri}">
        <i:Interaction.Triggers>
          <i:EventTrigger EventName="Remove">
            <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:Mirage}}, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}" />
          </i:EventTrigger>
        </i:Interaction.Triggers>
      </controls:DropableImage>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

绑定的数据类型为ImageBar

using Controller.Core.ImageHandler;
using Prism.Mvvm;
using System.Windows.Media.Imaging;

namespace Controller.Extensions.Bar.Home
{
    public class ImageBar:BindableBase
    {
        private BitmapSource imageUri;
        private string title;
        private int index;


        public string Title { 
            get { return title; } 
            set { title = value; RaisePropertyChanged(); }
        }
        public BitmapSource ImageUri { 
            get { return imageUri; } 
            set { imageUri = value;RaisePropertyChanged();}
        }
        public int Index
        {
            get => index;
            set => SetProperty(ref index,value);
        }

        public ImageBar()
        {
            imageUri = DefaultImage.Undefined;
            title = "Undefined";
        }

        public ImageBar(BitmapSource source)
        {
            imageUri = source;
            title=System.IO.Path.GetFileNameWithoutExtension(source.ToString());
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

截图
在这里插入图片描述
数据总算是能正确修改了,唯一的瑕疵就是不知道为什么第一个会出现问题,展不开(自定义控件还挺好玩?)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/735192
推荐阅读
相关标签
  

闽ICP备14008679号