自己随便瞎写写遇到的离谱问题,还是写一下以免自己下一次又手足无措
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>
控件截图
在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());
}
}
}
截图
数据总算是能正确修改了,唯一的瑕疵就是不知道为什么第一个会出现问题,展不开(自定义控件还挺好玩?)