2016-07-29 67 views
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)以及当它载入较重的图像时,它经常会引发我的异常。

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

+0

你已经得到的[答案](http://stackoverflow.com/questions/29097171/sharing-violation-on-image-view-and-edit-wpf),只是改变图像加载机制在给定的地方提供'ImageItem',所以它会创建读文件并将其复制到内存中。关于记忆,WPF在图像处理方面是完全“垃圾”,因为它的图像质量。您的5Mb jpeg可能会在内存中增长到〜100Mb,因此最好在有人切换时从文件中加载图像,并在其他时间仅保留图像的文件路径。 – Shakra

+0

@Shakra所以可能我在执行时可能会出错。处理BitmapSource,BitmapImage和ImageItem可能是问题。你可以请张贴的方式? – iamCR

回答

1

您需要为编辑器观众

模型需要更新的视图模型,因为它会改变(你可以用模型来构建视图模型做不同的事情一点点,但是在这种情况下,有示范这么少的功能,你还不如用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

接下来我们有编辑器本身

文件
<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(); 
    } 
} 

注意,如果你正在做的任何缩放/拉伸图片,那么你需要使它们或之前扭转对笔画的操作或修改将在相对于屏幕没有图像

+0

加载图片我在'ImageFrame = BitmapFrame.Create(mem)'行中得到'FileFormatException'。错误是'图像无法解码。图像头可能已损坏。 – iamCR

+0

错误是说文件不是有效的图像,首先检查错误发生时路径是否正确,如果路径无效指向错误的文件以解释错误,则可能需要重置内存流的位置,它在我的测试中工作,但有时需要在创建帧之前重置'mem.Position = 0;' – MikeT

+0

使用第二种方法。但编辑后未启用保存/撤销。你能看到吗?我不知道如何使用'mspaint'工作。可以? – iamCR