1

我正在使用 MVVM 处理 WPF 应用程序。在 imageSlider 中加载和编辑图像会给我错误和异常。

加载图像:Insufficient memory to handle 编辑图像:Sharing violation

视图模型:

public class ImageList : INotifyPropertyChanged
    {
        #region Fields
    private ObservableCollection<ImageItem> images = new ObservableCollection<ImageItem>();
    private int selectedIndex;
    private ImageItem currentImage;

    #endregion Fields

    #region Properties

    public ObservableCollection<ImageItem> Images
    {
        get { return images; }
        set { images = value; }
    }

    public int SelectedIndex
    {
        get { return selectedIndex; }
        set
        {
            if(value < Images.Count && value > -1)
            {
                selectedIndex = value; OnPropertyChanged();
                CurrentImage = Images[selectedIndex];
            }
        }
    }

    public ImageItem CurrentImage
    {
        get { return currentImage; }
        set { currentImage = value; OnPropertyChanged(); }
    }

    #endregion Properties

    #region Public Methods

    public void Next()
    {
        SelectedIndex ++;
    }

    public void Back()
    {
        SelectedIndex--;
    }

    #endregion Public Methods

    #region Methods

    public void AddNewImage()
    {
        OpenFileDialog dlg = new OpenFileDialog();
        dlg.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.gif, *.png, *.bmp, *.tif) | *.jpg; *.jpeg; *.jpe; *.gif; *.png, *.bmp, *.tif";
        dlg.ShowDialog();

        if(dlg.FileName != "")
        {
            Images.Add(new ImageItem() { URI = new Uri(dlg.FileName) });
            SelectedIndex = Images.Count - 1;
        }
    }

    #endregion Methods

    #region Constructors

    public ImageList()
    {
        _canExecute = true;
    }

    #endregion Constructors

    #region Commands

    private ICommand _clickCommand;
    public ICommand ClickCommand
    {
        get
        {
            return _clickCommand ?? (_clickCommand = new CommandHandler(() => AddNewImage(), _canExecute));
        }
    }

    private bool _canExecute;

    private ICommand nextCommand;

    public ICommand NextCommand
    {
        get
        {
            if (nextCommand == null)
            {
                nextCommand = new CommandHandler(()=> OnNextCommand(), true);
            }
            return nextCommand;
        }
        set { nextCommand = value; }
    }

    private void OnNextCommand()
    {
        Next();
    }
    private ICommand backCommand;

    public ICommand BackCommand
    {
        get
        {
            if(backCommand == null)
            {
                backCommand = new CommandHandler(() => OnBackCommand(), true);
            }
            return backCommand;
        }
        set { backCommand = value; }
    }

    private void OnBackCommand()
    {
        Back();
    }

 private ICommand _clickCommand;
        public ICommand ClickCommand
        {
            get
            {
                return _clickCommand ?? (_clickCommand = new CommandHandler(() => AddNewImage(), _canExecute));
            }
        }

        private ICommand _EditImageCommand;
        public ICommand EditImageCommand
        {
            get
            {
                return _EditImageCommand ?? (_EditImageCommand = new CommandHandler(obj => EditImage(obj), _canExecute));
            }
        }

    #endregion Commands

 public void EditImage(object obj)
        {
            string ss = ((ImageItem)obj).URI.AbsolutePath;
            Process proc = Process.Start(ss);
            if (proc != null)
            {
                proc.EnableRaisingEvents = true;
                ProcessStartInfo startInfo = new ProcessStartInfo();
                //startInfo.Verb = "edit";
                startInfo.FileName = ("mspaint.exe");
                proc.StartInfo = startInfo;

            }
        }


    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChanged

}`

模型:

 public class ImageItem
    {
        public Uri URI { get; set; }

        private BitmapSource _Source;

        public BitmapSource Source
        {
            get
            {
                try
                {
                    if (_Source == null) _Source = new BitmapImage(URI);//lazy loading

                }
                catch (Exception)
                {
                    _Source = null;
                }
                return _Source;
            }
        }

        public void Save(string filename)
        {
            var img = BitmapFrame.Create(Source);
            var encoder = new JpegBitmapEncoder();

            encoder.Frames.Add(img);
            using (var saveStream = System.IO.File.OpenWrite(filename))
                encoder.Save(saveStream);

        }


    }

XAML

<DockPanel>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="30"></RowDefinition>
                <RowDefinition Height="200"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="30"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="30"></ColumnDefinition>
            </Grid.ColumnDefinitions>

        <Button Content="&lt;" Command="{Binding BackCommand}" Width="25" Grid.Row="0" Grid.Column="0"/>
            <Button DockPanel.Dock="Right" Content="&gt;" Command="{Binding NextCommand}" Width="25" Grid.Row="0" Grid.Column="2"/>
            <Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="0" Grid.Column="1">
                <Image.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Edit Image" Command="{Binding EditImageCommand, Source={StaticResource SliderViewModel}}" CommandParameter="{Binding CurrentImage}"></MenuItem>
                    </ContextMenu>
                </Image.ContextMenu>
            </Image>
            <Button Content="Add" Command="{Binding ClickCommand}" Height="30" Width="50" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4"></Button>
            <!--<Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="2" Grid.Column="1"/>-->
        </Grid>
    </DockPanel>

上面的代码适用于 imageSlider(Next 和 Back)。但是在编辑过程中它会打开mspaint,当我保存它时会抛出我Sharing violation error厌倦了在 SO herehere中发布的几个解决方案。尝试这些时,我可以编辑图像并保存而不会出现任何错误。但是当使用 Slider 控件(Next 和 Back)并加载较重的图像时,它经常会抛出Insufficient memory to handle异常。

所以现在我需要消除这两个问题。请帮忙。

4

1 回答 1

1

您需要为编辑者和查看者做一些不同的事情

您的模型需要更新为 ViewModel,因为它会发生变化(您可以使用模型来构建 ViewModel,但是在这种情况下,模型中的功能非常少,您不妨使用 FIleInfo 类作为模型)

public class ImageEditor: IDisposable,INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private List<FileInfo> images = new List<FileInfo>();

    private FileInfo _ImageFile;
    public static readonly PropertyChangedEventArgs FilenameProperty = new PropertyChangedEventArgs(nameof(ImageFile));

    public FileInfo ImageFile
    {
        get { return _ImageFile; }
        set
        {
            _ImageFile = value;
            Strokes.Clear();
            PropertyChanged?.Invoke(this, ImageFrameProperty);
        }
    }

    private int selectedIndex;
    public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs(nameof(SelectedIndex));
    public int SelectedIndex
    {
        get { return selectedIndex; }
        private set
        {
            if (value < images.Count && value > -1)
            {
                selectedIndex = value;
                PropertyChanged?.Invoke(this, SelectedIndexProperty);
                ImageFile = images[selectedIndex];
                Load();
            }
        }
    }

    MemoryStream mem;
    private BitmapSource _ImageFrame;
    public static readonly PropertyChangedEventArgs ImageFrameProperty = new PropertyChangedEventArgs(nameof(ImageFrame));
    public BitmapSource ImageFrame
    {
        get { return _ImageFrame; }
        set
        {
            _ImageFrame = value;
            PropertyChanged?.Invoke(this, ImageFrameProperty);
        }
    }

    public StrokeCollection Strokes { get;  } = new StrokeCollection();

    public void Open(FileInfo file)
    {
        images.Add(file);
        SelectedIndex = images.Count - 1;
    }
    public void Next()
    {
        SelectedIndex++;
    }

    public void Back()
    {
        SelectedIndex--;
    }

    public void Load()
    {
        ImageFile.Refresh();
        if (ImageFile.Exists)
        {
            if (mem != null)
            {
                mem.Dispose();
            }
            using (var stream = ImageFile.OpenRead())
            {
                mem = new MemoryStream();
                stream.CopyTo(mem);
            }
            ImageFrame = BitmapFrame.Create(mem);
        }
    }
    public void Dispose()
    {
        if (mem != null)
        {
            mem.Dispose();
        }
        ImageFrame = null;
    }

    public void Save()
    {

        DrawingVisual drawingVisual = new DrawingVisual();
        using (DrawingContext drawingContext = drawingVisual.RenderOpen())
        {

            drawingContext.DrawImage(ImageFrame, new Rect(0, 0, ImageFrame.Width, ImageFrame.Height));
            foreach (var item in Strokes)
            {
                item.Draw(drawingContext);
            }
            drawingContext.Close();
            Strokes.Clear();
            var width = ImageFrame.Width;
            var height = ImageFrame.Height;
            var bitmap = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
            bitmap.Render(drawingVisual);

            ImageFrame = bitmap;

            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(ImageFrame));

            using (var stream = ImageFile.OpenWrite())
            {
                encoder.Save(stream);
            }
        }
    }
}

注意:由于我们使用的是没有 using 语句的内存流,因此使用 IDisposable 接口进行清理对​​我们来说是个好主意

这样做是创建一个内存驻留位图,然后将其用作框架,这将删除您使用带有 URI 的普通位图获得的文件上的 openRead 锁定

接下来我们有编辑器本身

<Window x:Class="ImageDemo.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ImageDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" Closing="Window_Closing" >
    <Window.DataContext>
        <local:ImageEditor x:Name="editor" />

    </Window.DataContext>

    <DockPanel>
        <Menu DockPanel.Dock="Top" >
            <MenuItem Header="File" >
                <MenuItem Command="ApplicationCommands.Open" />
                <MenuItem Command="ApplicationCommands.Save" />
            </MenuItem>
            <MenuItem Command="ApplicationCommands.Undo" />
            <ComboBox SelectedValue="{Binding DefaultDrawingAttributes.Color, ElementName=inkCanvas}">
                <Color>White</Color>
                <Color>Black</Color>
                <Color>Yellow</Color>
                <Color>Red</Color>
                <Color>Cyan</Color>
                <Color>SpringGreen</Color>
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <Rectangle Width="15" Height="15">
                            <Rectangle.Fill>
                                <SolidColorBrush Color="{Binding Mode=OneWay}" />
                            </Rectangle.Fill>
                        </Rectangle>
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
        </Menu>
        <Button Content="&lt;" Click="Back_Click"/>
        <Button Content="&gt;" DockPanel.Dock="Right" Click="Next_Click"/>
        <Grid HorizontalAlignment="Left" VerticalAlignment="Top" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
                <Image Source="{Binding ImageFrame}" Stretch="None"/>
                <InkCanvas x:Name="inkCanvas" Background="Transparent" Strokes="{Binding Strokes}" >
                    <InkCanvas.DefaultDrawingAttributes>
                        <DrawingAttributes x:Name="DrawSetting" />
                    </InkCanvas.DefaultDrawingAttributes>
                </InkCanvas>
            </Grid>
    </DockPanel>
</Window>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, execSave, hasChanged));
        CommandBindings.Add(new CommandBinding(ApplicationCommands.Open, execOpen));
        CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, execUndo, hasChanged));
    }

    private void execOpen(object sender, ExecutedRoutedEventArgs e)
    {
        OpenFileDialog dlg = new OpenFileDialog();

        if(dlg.ShowDialog() ?? false)
        {
            editor.Open(new System.IO.FileInfo(dlg.FileName));
        }
        e.Handled = true;
    }

    private void hasChanged(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = editor.Strokes.Count > 0;
        e.Handled = true;
    }

    private void execUndo(object sender, ExecutedRoutedEventArgs e)
    {
        editor.Strokes.Remove(editor.Strokes.Last());
        e.Handled = true;
    }

    private void execSave(object sender, ExecutedRoutedEventArgs e)
    {
        editor.Save();
        e.Handled = true;
    }

    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        editor.Dispose();
    }

    private void Next_Click(object sender, RoutedEventArgs e)
    {
        editor.Next();
    }
    private void Back_Click(object sender, RoutedEventArgs e)
    {
        editor.Back();
    }
}

请注意,如果您正在对图像进行任何缩放/拉伸,那么您需要在渲染它们之前反转对 Stroke 的操作,否则编辑将与屏幕而不是图像相关

于 2016-07-29T10:54:32.457 回答