[Wpf] 前端元素变化但后端数据没变的解决方法

文章讲述了在WPF应用中,如何处理ListBox中的ImageUri属性在拖放事件后未能同步更新的问题,通过分析发现是由于数据绑定和UI实例化的区别导致的,最终通过自定义控件和依赖属性解决了这个问题。
摘要由CSDN通过智能技术生成

自己随便瞎写写遇到的离谱问题,还是写一下以免自己下一次又手足无措
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>

ItemSource中的Images的定义为:

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

然后,在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);
    }
}

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

问题分析

根据gpt的分析

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

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

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

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

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

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

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

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

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

可以的,结果出来了
在这里插入图片描述
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>

我感觉不可能出这种问题,有大佬能告诉我该怎么用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>

控件截图
基本和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);

        }
    }
}

(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>

绑定的数据类型为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());
        }
    }
}

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

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
WPF,可以使用XAML和C#代码来实现渐淡入淡出的动画效果。以下是一个简单的示例: XAML代码: ```xml <Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Rectangle x:Name="myRect" Width="100" Height="100" Fill="Red"/> </Grid> </Window> ``` C#代码: ```csharp using System.Windows; using System.Windows.Media.Animation; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // 创建一个双向动画,将颜色从红色渐到蓝色,然后再从蓝色渐回红色 ColorAnimation colorAnimation = new ColorAnimation(); colorAnimation.From = Colors.Red; colorAnimation.To = Colors.Blue; colorAnimation.Duration = new Duration(TimeSpan.FromSeconds(1)); colorAnimation.AutoReverse = true; colorAnimation.RepeatBehavior = RepeatBehavior.Forever; // 创建一个双向动画,将矩形的不透明度从0渐到1,然后再从1渐回0 DoubleAnimation opacityAnimation = new DoubleAnimation(); opacityAnimation.From = 0.0; opacityAnimation.To = 1.0; opacityAnimation.Duration = new Duration(TimeSpan.FromSeconds(1)); opacityAnimation.AutoReverse = true; opacityAnimation.RepeatBehavior = RepeatBehavior.Forever; // 将动画应用到矩形的背景色和不透明度属性上 myRect.BeginAnimation(Rectangle.FillProperty, colorAnimation); myRect.BeginAnimation(Rectangle.OpacityProperty, opacityAnimation); } } } ``` 在这个示例,我们使用ColorAnimation和DoubleAnimation类创建了两个双向动画,分别用于矩形的背景色和不透明度属性。然后,我们将这两个动画应用到矩形的属性上,以实现渐淡入淡出的动画效果。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值