I'm working in WPF application with MVVM. Loading and Editing a image in imageSlider gives me error and exceptions.
Loading Image: Insufficient memory to handle
Editing Image: Sharing violation
ViewModel:
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
}`
Model:
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="<" Command="{Binding BackCommand}" Width="25" Grid.Row="0" Grid.Column="0"/>
<Button DockPanel.Dock="Right" Content=">" 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>
The code above works fine with the imageSlider(Next and Back). But during edit it opens in mspaint and when i save it throws me Sharing violation error. Tired few solutions posted in SO here and here . When trying these i can able to edit the image and save without any error. But when using the Slider control (Next and Back) and when it loads heavier images, frequently it throws me Insufficient memory to handle exception.
So now i need to eliminate both these issues. Kindly help.
You need to do things a little differently for an editor to a viewer
your Model needs to be updated to a ViewModel as it will be changing ( you can use the model to build the ViewModel, however in this case there is so little functionality in the Model that you might as well use the FIleInfo class as your model)
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);
}
}
}
}
note: as we are using a memory stream with out the using statement then its a good idea to us the IDisposable interface for clean up
what this is doing is creating a memory resident bitmap then using that as a Frame, this removes the openRead lock on on the file that you get with a normal bitmap with a URI
next we have the Editor itself
<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="<" Click="Back_Click"/>
<Button Content=">" 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();
}
}
Note if you are doing any scaling/stretching on the image then you need to reverse the operation on the Strokes before rendering them or or the edits will be in relation to the screen not the image
Related
I have a UserControl which contains a InkToolbar. In this control I'm generating another set of UserControls based on user input. In each of those programmatically generated controls, contains a InkCanvas.
This is the parent UserControl & the code behind.
Main.xaml
<UserControl
x:Class="uwp.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:uwp_mvvm"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<InkToolbar x:Name="InkToolbar" Background="Black" Margin="10 0 0 0" VerticalAlignment="Center"/>
</StackPanel>
<ScrollViewer ZoomMode="Enabled" Background="DarkGray" x:Name="ScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" MinZoomFactor="0.25" Width="Auto" Height="Auto" MaxZoomFactor="4" Grid.Row="1">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<ItemsControl x:Name="PagesItemsControl" ItemsSource="{x:Bind Pages, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:TestControl x:Name="TestControl" Page="{Binding}" Margin="4 4" InkToolbarControl="{Binding Path=InkToolbar, ElementName=InkToolbar}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
Main.xaml.cs
public sealed partial class MainViewer : UserControl
{
public MainViewer()
{
this.InitializeComponent();
}
private void EventTriggerChanged(object sender, PropertyChangedEventArgs e)
{
var trigger = sender as EventTriggerTestControl;
if (trigger != null)
RaiseChanged(e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
private PdfDocument pdfDocument = null;
private string password = string.Empty;
private ObservableCollection<PdfPage> pages = new ObservableCollection<PdfPage>();
public string Password
{
private get { return password; }
set { if (value != password) { password = value; } }
}
public ObservableCollection<PdfPage> Pages
{
get { return pages; }
set { if (value != pages) { pages = value; } }
}
public StorageFile File
{
get { return (StorageFile)GetValue(FileProperty); }
set { SetValue(FileProperty, value); }
}
public static readonly DependencyProperty FileProperty =
DependencyProperty.Register(nameof(File), typeof(StorageFile), typeof(MainViewer), new PropertyMetadata(null, new PropertyChangedCallback(OnDocumentChanged)));
private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
return;
d.RegisterPropertyChangedCallback(FileProperty, CaptureDocument);
}
private async static void CaptureDocument(DependencyObject sender, DependencyProperty dp) => await (sender as MainViewer).OpenFileAsync(sender.GetValue(dp) as StorageFile);
private async Task OpenFileAsync(StorageFile file)
{
try
{
if (file == null)
throw new ArgumentNullException(nameof(file));
var files = await ApplicationData.Current.LocalFolder.GetFilesAsync(CommonFileQuery.OrderByName);
if (files.Where(x => x.Name == file.Name).ToList().Count == 0)
await file.CopyAsync(ApplicationData.Current.LocalFolder, file.Name, NameCollisionOption.ReplaceExisting);
file = await ApplicationData.Current.LocalFolder.GetFileAsync(file.Name);
pdfDocument = new PdfDocument(file.Path, Password);
pdfDocument.Pages.ToList().ForEach(x => Pages.Add(x));
}
catch (Exception ex) { throw ex; }
}
}
I want to use the InkToolbar which is in the parent control inside the TestControl
TestControl.xaml
<UserControl
x:Class="uwp.TestControl"
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="using:uwp_mvvm.ViewModels" xmlns:converter="using:uwp_mvvm"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</UserControl.Resources>
<Grid x:Name="Viewport" VerticalAlignment="Top" HorizontalAlignment="Center">
<Border x:Name="ViewportBorder" Background="White" BorderThickness="2, 2, 2, 2" BorderBrush="#FF353334" />
<Image x:Name="Image" Margin="0" UseLayoutRounding="True" ManipulationMode="Scale"/>
<Canvas x:Name="SelectionCanvas" CompositeMode="MinBlend" Opacity="1" />
<InkCanvas x:Name="InkCanvas" />
</Grid>
</UserControl>
TastControl.xaml.cs
public sealed partial class TestControl : UserControl
{
private const float DPI = 256;
public PdfPage Page
{
get { return (PdfPage)GetValue(PageProperty); }
set { SetValue(PageProperty, value); }
}
public static readonly DependencyProperty PageProperty =
DependencyProperty.Register(nameof(Page), typeof(PdfPage), typeof(TestControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPageChanged)));
private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
return;
d.RegisterPropertyChangedCallback(PageProperty, CapturePage);
}
private static void CapturePage(DependencyObject sender, DependencyProperty dp) => (sender as TestControl).RenderPage(sender.GetValue(dp) as PdfPage);
public InkToolbar InkToolbarControl
{
get { return (InkToolbar)GetValue(InkToolbarControlProperty); }
set { SetValue(InkToolbarControlProperty, value); }
}
public static readonly DependencyProperty InkToolbarControlProperty =
DependencyProperty.Register(nameof(InkToolbarControl), typeof(InkToolbar), typeof(TestControl), new PropertyMetadata(null));
public TestControl()
{
this.InitializeComponent();
ConfigureInkCanvas();
}
private void ConfigureInkCanvas()
{
InkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch;
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
InkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(
new InkDrawingAttributes
{
IgnorePressure = false,
FitToCurve = true,
Color = Colors.Black,
PenTip = PenTipShape.Circle
});
InkCanvas.InkPresenter.UnprocessedInput.PointerEntered -= InkCanvasUnprocessedInputPointerEntered;
InkCanvas.InkPresenter.UnprocessedInput.PointerEntered += InkCanvasUnprocessedInputPointerEntered;
InkCanvas.InkPresenter.UnprocessedInput.PointerExited -= InkCanvasUnprocessedInputPointerExited;
InkCanvas.InkPresenter.UnprocessedInput.PointerExited += InkCanvasUnprocessedInputPointerExited;
}
private void InkCanvasUnprocessedInputPointerExited(InkUnprocessedInput sender, PointerEventArgs args)
{
//InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking;
//InkToolbarControl.TargetInkCanvas = InkCanvas;
}
private void InkCanvasUnprocessedInputPointerEntered(InkUnprocessedInput sender, PointerEventArgs args)
{
//InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
//InkToolbarControl.TargetInkCanvas = null;
}
private void RenderPage(PdfPage page, float dpi = DPI)
{
if (page is null)
return;
this.DataContext = page;
var width = (DataContext as PdfPage).Width;
var height = (DataContext as PdfPage).Height;
var writableBitmap = new WriteableBitmap((int)(width / 72f * dpi), (int)(height / 72f * dpi));
(DataContext as PdfPage).Render(writableBitmap, PageOrientations.Normal, RenderingFlags.Annotations);
Image.Width = width;
Image.Height = height;
Image.Source = writableBitmap;
SelectionCanvas.Width = width;
SelectionCanvas.Height = height;
InkCanvas.Width = width;
InkCanvas.Height = height;
Canvas.SetZIndex(Image, 0);
ConfigureInkCanvas();
}
}
I tried to use a DependencyProperty to send the InkToolbar to the TestControl but it didn't seem to work.
Could someone please help me with this? I'm not sure whether it is possible to set the TargetInkCanvas like this also. So, if there is a better approach for this any suggestions are also appreciated.
Any help is much appreciated. Thanks.
Access InkToolbar placed in parent element from programmatically generated child elements
Above solution that use DependencyProperty to pass parent InkToolBar is correct. However you bind wrong value for InkToolbarControl property.
Please bind ElementName without path directly.
<local:TestControl
x:Name="TestControl"
Margin="4,4"
InkToolbarControl="{Binding ElementName=InkToolbar}" />
Then update InkCanvasUnprocessedInputPointerExited and InkCanvasUnprocessedInputPointerEntered process logic.
private void InkCanvasUnprocessedInputPointerExited(InkUnprocessedInput sender, PointerEventArgs args)
{
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking;
InkToolbarControl.TargetInkCanvas = null;
}
private void InkCanvasUnprocessedInputPointerEntered(InkUnprocessedInput sender, PointerEventArgs args)
{
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
InkToolbarControl.TargetInkCanvas = InkCanvas;
}
I'm creating a WPF Molds app that contains 2 windows: MainWindow with DataGrid, and AddEditWindow which allows to Add/Edit Molds.
I have a EditButton which located in a TemplateColumn of DataGrid:
<DataGridTemplateColumn Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="150"
Height="40"
BorderThickness="2"
BorderBrush="DarkRed"
Background="Red"
Foreground="White"
Content="Edit"
Name="BtnEdit"
CommandParameter="{Binding}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.AddEditWindowCommand}">
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
AddEditWindowCommand:
public ICommand AddEditWindowCommand { get; }
private bool CanAddEditWindowCommandExecute(object SelectedRow) => true;
private void OnAddEditWindowCommandExecuted(object SelectedRow)
{
AddEditWindow window = new AddEditWindow();
window.Show();
}
And I want to pass DataContext to the AddEditWindowViewModel. In a Code-Behind, I could done something like this:
private void BtnEdit_Click(object sender, RoutedEventArgs e)
{
AddEditWindow addEditWindow = new AddEditWindow((sender as Button).DataContext as Molds);
addEditWindow.Show();
}
And then retrieve it AddEditWindow like this:
private Molds _currentMold = new Molds();
public GamesEdit(Molds selectedMold)
{
InitializeComponent();
if (selectedMold != null)
{
_currentMold = selectedMold;
}
DataContext = _currentMold;
But in MVVM I can't. So, is there a way to do it without breaking MVVM pattern?
p.s. since I'm new to the MVVM, I would really appreciate detailed explanation.
update:
MainWindowViewModel:
internal class MainWindowViewModel : ViewModel
{
#region Variables
#region Textblocks for search
private Molds newMolds { get; set; } = new Molds();
public string TxtType
{
get => newMolds.Type;
set => newMolds.Type = value;
}
public string TxtName
{
get => newMolds.Name;
set => newMolds.Name = value;
}
public string TxtKus
{
get => newMolds.Kus;
set => newMolds.Kus = value;
}
#endregion
#region AllMolds
private ObservableCollection<Molds> allMolds = new ObservableCollection<Molds>(ApplicationContext.GetContext().Molds.ToList());
public ObservableCollection<Molds> AllMolds
{
get => allMolds;
set => allMolds = value;
}
#endregion
#region FilteredMolds
private ObservableCollection<Molds> filteredMolds = new ObservableCollection<Molds>(ApplicationContext.GetContext().Molds.ToList());
public ObservableCollection<Molds> FilteredMolds
{
get
{
filteredMolds = AllMolds;
var currentfilteredmolds = new List<Molds>(filteredMolds);
if (TxtName != null)
currentfilteredmolds = currentfilteredmolds.Where(p => p.Name.ToLower().Contains(TxtName.ToLower())).ToList();
if (TxtType != null)
currentfilteredmolds = currentfilteredmolds.Where(p => p.Type.ToLower().Contains(TxtType.ToLower())).ToList();
if (TxtKus != null)
currentfilteredmolds = currentfilteredmolds.Where(p => p.Kus.ToLower().Contains(TxtKus.ToLower())).ToList();
return new ObservableCollection<Molds>(currentfilteredmolds);
}
set => filteredMolds = value;
}
#endregion
#endregion
#region Commands
#region CloseApplicationCommand
public ICommand CloseApplicationCommand { get; }
private bool CanCloseApplicationCommandExecute(object p) => true;
private void OnCloseApplicationCommandExecuted(object p)
{
Application.Current.Shutdown();
}
#endregion
#region SearchCommand
public ICommand SearchCommand { get; }
private bool CanSearchCommandExecute(object p) => true;
private void OnSearchCommandExecuted(object p)
{
OnPropertyChanged("FilteredMolds");
}
#endregion
#region Open AddEditWindowCommand
public ICommand AddEditWindowCommand { get; }
private bool CanAddEditWindowCommandExecute(object SelectedRow) => true;
private void OnAddEditWindowCommandExecuted(object SelectedRow)
{
AddEditWindow window = new AddEditWindow();
window.Show();
}
#endregion
#region DeleteMoldCommand
public ICommand DeleteMoldCommand { get; }
private bool CanDeleteMoldCommandExecute(object SelectedItems)
{
if (SelectedItems != null) return true; else return false;
}
private void OnDeleteMoldCommandExecuted(object SelectedItems)
{
System.Collections.IList items = (System.Collections.IList)SelectedItems;
var moldsforRemoving = items?.Cast<Molds>().ToList();
if (MessageBox.Show($"You want to remove the following {moldsforRemoving.Count()} molds?", "Attention",
MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
try
{
ApplicationContext.GetContext().Molds.RemoveRange(moldsforRemoving);
ApplicationContext.GetContext().SaveChanges();
MessageBox.Show("Data deleted successfully.", "Data deletion",
MessageBoxButton.OK, MessageBoxImage.Information);
AllMolds = new ObservableCollection<Molds>(ApplicationContext.GetContext().Molds.ToList());
OnPropertyChanged("FilteredMolds");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString(), "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
#endregion
#region DragMoveCommand
public ICommand DragMoveCommand { get; }
private bool CanDragMoveCommandExecute(object p) => true;
private void OnDragMoveCommandExecuted(object p)
{
Application.Current.MainWindow.DragMove();
}
#endregion
#endregion
public MainWindowViewModel()
{
#region Command Samples
CloseApplicationCommand = new LamdaCommand(OnCloseApplicationCommandExecuted, CanCloseApplicationCommandExecute);
SearchCommand = new LamdaCommand(OnSearchCommandExecuted, CanSearchCommandExecute);
AddEditWindowCommand = new LamdaCommand(OnAddEditWindowCommandExecuted, CanAddEditWindowCommandExecute);
DeleteMoldCommand = new LamdaCommand(OnDeleteMoldCommandExecuted, CanDeleteMoldCommandExecute);
DragMoveCommand = new LamdaCommand(OnDragMoveCommandExecuted, CanDragMoveCommandExecute);
#endregion
#region Variable Samples for searching
TxtName = null;
TxtKus = null;
TxtType = null;
#endregion
}
}
AddEditWindowViewModel
internal class AddEditWindowViewModel : ViewModel
{
#region Variables
private Molds _currentMold = new Molds();
#endregion
#region Commands
#region CloseWindowCommand
public ICommand CloseWindowCommand { get; }
private bool CanCloseWindowCommandExecute(object p) => true;
private void OnCloseWindowCommandExecuted(object p)
{
Application.Current.Windows[1].Close();
}
#endregion
#region DragMoveAddEditWindowCommand
public ICommand DragMoveAddEditWindowCommand { get; }
private bool CanDragMoveAddEditWindowCommandExecute(object p) => true;
private void OnDragMoveAddEditWindowCommandExecuted(object p)
{
Application.Current.Windows[1].DragMove();
}
#endregion
#endregion
public AddEditWindowViewModel()
{
#region Command samples
CloseWindowCommand = new LamdaCommand(OnCloseWindowCommandExecuted, CanCloseWindowCommandExecute);
DragMoveAddEditWindowCommand = new LamdaCommand(OnDragMoveAddEditWindowCommandExecuted, CanDragMoveAddEditWindowCommandExecute);
#endregion
}
}
I connect them to the window using <Window.DataContext>:
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
Same goes for the AddEditWindowViewModel.
DataGrid binding:
<DataGrid x:Name="DGridMolds"
AutoGenerateColumns="False"
IsReadOnly="True"
Foreground="White"
BorderBrush="White"
Background="#2b2a38"
Grid.Column="1"
Grid.Row="1"
ItemsSource="{Binding Path=FilteredMolds}"
>
AddEditWindow.Xaml:
<Window.DataContext>
<vm:AddEditWindowViewModel/>
</Window.DataContext>
<Border Background="#2f2e3c"
CornerRadius="10">
<Border.InputBindings>
<MouseBinding Command="{Binding DragMoveAddEditWindowCommand}" MouseAction="LeftClick"/>
</Border.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Grid.Row="1" Orientation="Vertical">
<TextBlock Text="Add" FontSize="22" Foreground="White" HorizontalAlignment="Center" Margin="0,30,0,0"/>
<TextBox Foreground="White" Text="{Binding Type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5, 35, 5, 5" materialDesign:HintAssist.Hint="Type"/>
<TextBox Foreground="White" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5" materialDesign:HintAssist.Hint="Name"/>
<TextBox Foreground="White" Text="{Binding Kus, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5" materialDesign:HintAssist.Hint="Kus"/>
In a Code-Behind, I could done something like this:
Take a closer look at your XAML.
You have a binding set in the CommanParameter property.
The binding instance is empty - this means that the binding occurs to the DataContext of the element in which it is specified.
Hence, in the command parameter, you will get this Data Context.
private void OnAddEditWindowCommandExecuted(object SelectedRow)
{
AddEditWindow window = new AddEditWindow()
{DataContext = SelectedRow};
window.Show();
}
So, is there a way to do it without breaking MVVM pattern?
You've already broken MVVM.
ViewModel is not allowed to work with UI elements.
The ViewModel doesn't even need to know what type of View is using it: WPF, Forms, or Console.
And you CREATE the Window!
This is unacceptable for the MVVM concept.
Addition in connection with showing more detailed code in the main question:
I can't understand the logic of your code.
Therefore, I will write in the measure as I understand your intentions.
AddEditWindowViewModel class - designed for logic for editing and/or adding an item.
But then he has to get this element and provide it in his property so that he can create a GUI for editing.
It should be something like this:
namespace MoldsApp
{
public class AddEditWindowViewModel : ViewModel
{
public Molds CurrentMold { get; }
// Constructor called to edit an entity
public AddEditWindowViewModel(Molds currentMold)
{
CurrentMold = currentMold;
}
//Constructor called to create and edit an entity
public AddEditWindowViewModel()
: this(new Molds())
{
}
}
}
Also, your DataContext is set incorrectly.
By creating it inside XAML, you cannot set the data for this instance of the ViewModel.
Therefore, in XAML, you can instantiate a ViewModel used only at the time of its designed.
For a design-time DataContext, use the d: prefix.
<d:Window.DataContext>
<vm:MainWindowViewModel/>
</d:Window.DataContext>
With these changes, the item edit command should be like this:
private void OnAddEditWindowCommandExecuted(object SelectedRow)
{
AddEditWindow window = new AddEditWindow()
{
DataContext = new AddEditWindowViewModel((Molds)SelectedRow)
};
window.Show();
}
I have a WPF app with 2 windows.
MainWindow.xaml
<Window x:Class="Procect1.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:Procect1"
mc:Ignorable="d"
Title="Lista pacjentów" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<DataGrid Name="listView" ItemsSource="{Binding ListOfPeople}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Imię" />
<DataGridTextColumn Binding="{Binding Surname}" Header="Nazwisko" />
<DataGridTextColumn Binding="{Binding IdNumber}" Header="Pesel" />
<DataGridTextColumn Binding="{Binding Age}" Header="Wiek" />
<DataGridTemplateColumn Header="Zdjęcie">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Image}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Canvas>
<Button Canvas.Right="20" Canvas.Top="30" Height="50" Width="80" FontSize="18" Command="{Binding Window2Open}">Dodaj</Button>
<Button Canvas.Right="20" Canvas.Top="100" Height="50" Width="80" FontSize="18" Command="{Binding Serialize}">Serializuj</Button>
<Button Canvas.Right="20" Canvas.Top="170" Height="50" Width="80" FontSize="18" Command="{Binding DeSerialize}">Załaduj</Button>
</Canvas>
</Grid>
</Window>
ViewModel.cs
namespace Procect1
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
ListOfPeople = new ObservableCollection<Person>();
}
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void ChangeDataExecute()
{
}
private void Window3OpenExecute()
{
Window3 Window3 = new Window3(this);
Window3.Show();
}
private void Window2OpenExecute()
{
Window2 Window2 = new Window2(this);
Window2.Show();
}
private void AddPhotoExecute()
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(Window2))
{
BitmapImage myBitmapImage = new BitmapImage();
myBitmapImage.BeginInit();
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
(window as Window2).imgPhoto.Source = new BitmapImage(new Uri(op.FileName));
}
}
}
}
private void AddPersonToListExecute()
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(Window2))
{
ListOfPeople.Add(new Person()
{
Name = (window as Window2).textBox_FirstName.Text,
Surname = (window as Window2).textBox_LastName.Text,
IdNumber = (window as Window2).textBox_IdNumber.Text,
Age = (window as Window2).textBox_Age.Text,
Image = (window as Window2).imgPhoto.Source
}) ;
(window as Window2).Close();
}
}
}
private void SerializeExecute()
{
Stream stream = File.OpenWrite(Environment.CurrentDirectory + "\\Serialization1.xml");
XmlSerializer people = new XmlSerializer(typeof(ObservableCollection<Person>));
people.Serialize(stream, ListOfPeople);
stream.Close();
}
private void DeSerializeExecute()
{
XmlSerializer deserializer = new XmlSerializer(typeof(ObservableCollection<Person>));
TextReader textReader = new StreamReader(Environment.CurrentDirectory + "\\Serialization1.xml");
ListOfPeople = (ObservableCollection<Person>)deserializer.Deserialize(textReader);
textReader.Close();
}
public ICommand ChangeData { get { return new RelayCommand(ChangeDataExecute); } }
public ICommand Window2Open { get { return new RelayCommand(Window2OpenExecute); } }
public ICommand Window3Open { get { return new RelayCommand(Window3OpenExecute); } }
public ICommand AddPersonToList { get { return new RelayCommand(AddPersonToListExecute); } }
public ICommand Serialize { get { return new RelayCommand(SerializeExecute); } }
public ICommand DeSerialize { get { return new RelayCommand(DeSerializeExecute); } }
public ICommand AddPhoto { get { return new RelayCommand(AddPhotoExecute); } }
public ObservableCollection<Person> ListOfPeople
{
get
{
return listOfPeople;
}
set
{
listOfPeople = value;
OnPropertyChanged("ListOfPeople");
}
}
private ObservableCollection<Person> listOfPeople;
public event PropertyChangedEventHandler PropertyChanged;
}
}
Person
namespace Procect1
{
public class Person : INotifyPropertyChanged
{
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public string Surname
{
get
{
return surname;
}
set
{
surname = value;
OnPropertyChanged(nameof(Surname));
}
}
public string IdNumber
{
get
{
return idNumber;
}
set
{
idNumber = value;
OnPropertyChanged(nameof(IdNumber));
}
}
public string Age
{
get
{
return age;
}
set
{
age = value;
OnPropertyChanged(nameof(Age));
}
}
public ImageSource Image
{
get
{
return Image;
}
set
{
image = value;
OnPropertyChanged(nameof(Image));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string name;
private string surname;
private string idNumber;
private string age;
private ImageSource image;
}
}
In the Window2, I write first name, last name, id number, age, and I add a photo of a person.
In the MainWindow, I have to show the information about added people in datagrid. My problem is that I don't know what to do the app would show added photo in datagrid- in class Person.cs shows up the OverflowException after adding person(return Image)
Thank you very much in advance!
My CustomControl changes the Visibility of it's children when i assign a value to a child. Here's my code
<UserControl x:Class="CrossProjectUserControls.controls.OnOffSwitch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CrossProjectUserControls.controls"
mc:Ignorable="d" Background="White" Loaded="UserControl_Loaded">
<Grid>
<Image x:Name="imgSwitch" HorizontalAlignment="Left" MaxHeight="30" MinWidth="80" PreviewMouseLeftButtonDown="imgSwitch_PreviewMouseLeftButtonDown" Source="/CrossProjectUserControls;component/res/icons/onoffswitch/switch_off_1_V3_100x40.png" Margin="3,3,0,3"/>
<Label x:Name="lblText" Content="{Binding Text}" Margin="83,3,5,5" VerticalAlignment="Center" FontFamily="Arial" FontSize="16" Background="#FF633C3C"/>
</Grid>
public partial class OnOffSwitch : UserControl {
private BitmapImage bmpOn = new BitmapImage(new Uri(#"/MentorPlus;component/res/icons/onoffswitch/switch_off_1_V3_100x40.png", UriKind.Relative));
private BitmapImage bmpOff = new BitmapImage(new Uri(#"/MentorPlus;component/res/icons/onoffswitch/switch_on_1_V3_100x40.png", UriKind.Relative));
private bool isOn = false;
private string text = "Debug";
public bool IsOn
{
get { return isOn; }
set
{
isOn = value;
ReloadUI();
}
}
public string Text
{
get { return text; }
set { text = value; lblText.Content = text; }
}
public static readonly RoutedEvent SwitchEvent = EventManager.RegisterRoutedEvent(
"Switched", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OnOffSwitch));
public OnOffSwitch() {
InitializeComponent();
}
private void InitializeControls() {
//this.lblText.Content = text;
//this.imgSwitch.Source = bmpOff;
}
public void Switch() {
IsOn = !IsOn;
}
private void ReloadUI() {
if (this.IsOn == true) {
this.imgSwitch.Source = bmpOn;
} else if (this.IsOn == false) {
this.imgSwitch.Source = bmpOff;
}
}
public event RoutedEventHandler Switched
{
add { AddHandler(SwitchEvent, value); }
remove { RemoveHandler(SwitchEvent, value); }
}
void RaiseSwitchEvent() {
RoutedEventArgs newEventArgs = new RoutedEventArgs(OnOffSwitch.SwitchEvent);
RaiseEvent(newEventArgs);
}
#region Events
private void imgSwitch_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
Switch();
RaiseSwitchEvent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
InitializeControls();
}
#endregion
}
So the Label is totally hidden if i assign a string to it's content like this.lblText.Content = "Test". Is there something I am missing or did I do something wrong?
Thanks for every help
I have a Silverlight project I'm converting to MVVM. It's my first time using the pattern and I'm struggling with something.
So basically I had this in the XAML code behind page:
OpenFileDialog ofd = new OpenFileDialog();
if ((bool)ofd.ShowDialog())
{
_fileName = ofd.File.Name;
FileStream fs = ofd.File.OpenRead();
fileSize = (double)fs.Length;
txtFileName.Text = fileName;
index = 0;
sendData = 0;
byte[] file = new byte[fs.Length];
fs.Read(file, 0, file.Length);
//convertToChunks(file);
prgUpload.Maximum = fileChunks.Count;
prgUpload.Value = 0;
//uploadChunks(index);
}
And I cannot figure out how to wire it up to be able to use that in the model? I assume the viewmodel comes into play, but nothing is working.
Any thoughts?
Here is the work in progress XAML:
<Grid x:Name="LayoutRoot" Width="475" Height="340">
<Canvas Margin="8,8,0,0" Background="White" Height="320" VerticalAlignment="Top" HorizontalAlignment="Left" Width="475">
<Button Width="75" Canvas.Left="380" Canvas.Top="43" Content="Browse" x:Name="btnBrowse" />
<TextBox Canvas.Left="25" IsReadOnly="True" Canvas.Top="43" TextWrapping="Wrap" Width="350" Text="{Binding Path=FileUploadName}" x:Name="txtFileName" />
<ProgressBar Height="10" Width="350" Canvas.Left="25" Canvas.Top="99" x:Name="prgUpload" />
<my:Label Content="Please select a file to upload" Name="lblError" Canvas.Left="25" Canvas.Top="23" RenderTransformOrigin="0.133,-0.063" Width="220"/>
<my:Label x:Name="lblProgress" Canvas.Left="25" Canvas.Top="78" RenderTransformOrigin="0.133,-0.063" Width="220"/>
</Canvas>
</Grid>
Basically I want it to fire after the user selects a file to upload.
If you want to fire a command this would do the work for you
<Button Width="75" Canvas.Left="380" Canvas.Top="43" Content="Browse" x:Name="btnBrowse"
Command={Binding OpenFileCommand} />
in your code behind Constructor for example
partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext=new MainViewModel();
}
}
and in your ViewModel
public ICommand OpenFileCommand { get; set; }
public MainViewModel()
{
OpenFileCommand = new RelayCommand(OpenDialog) { IsEnabled = true };
}
private void OpenDialog()
{
OpenFileDialog ofd = new OpenFileDialog();
if ((bool)ofd.ShowDialog())
{
_fileName = ofd.File.Name;
FileStream fs = ofd.File.OpenRead();
fileSize = (double)fs.Length;
//txtFileName.Text = fileName;// Apply Binding
index = 0;
sendData = 0;
byte[] file = new byte[fs.Length];
fs.Read(file, 0, file.Length);
//convertToChunks(file);
prgUpload.Maximum = fileChunks.Count;
prgUpload.Value = 0;
//uploadChunks(index);
}
}
And the RelayCommand
public class RelayCommand:ICommand
{
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
private Action _handler;
public RelayCommand(Action handler)
{
_handler = handler;
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
in order to get the filename in your textbox you have to bind the textbox to to the view model. so that it will appear on the UI and also implement INotifyPropertyChanged. Also Look at this it would be helpful Silverlight MVVM