赞
踩
自己随便瞎写写遇到的离谱问题,还是写一下以免自己下一次又手足无措
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吗……
过了这么多天,想了一个有用的解法,算是解决问题,但总感觉不像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()); } } }
截图
数据总算是能正确修改了,唯一的瑕疵就是不知道为什么第一个会出现问题,展不开(自定义控件还挺好玩?)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。