Add Adorner to Image in WPF RichTextBox when loaded - c#

I have a RichTextBox control in an application I am developing which I am using along with a ToolBar to create a rich text editor. One feature I have implemented is the ability for a user to insert an Image, now it worth noting at this point that the output from the RichTextBox is RTF.
When the user inserts the Image I am using the following code to add the image to the Document and then add a ResizeAdorner (example from here RichTextBox Resizing Adorner) to the Image which allows the user to resize. When the user saves and loads the Document the size of the Image is persisted correctly.
private void BtnInsertImage_OnClick(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Multiselect = false;
ofd.CheckFileExists = true;
ofd.CheckPathExists = true;
ofd.Filter = "Image files (*.png;*.jpg;*.jpeg;*.gif;*.bmp)|*.png;*.jpg;*.jpeg;*.gif;*.bmp";
ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
ofd.Title = "Insert Image";
if (ofd.ShowDialog() == true)
{
BitmapImage bitmap = new BitmapImage(new Uri(ofd.FileName, UriKind.RelativeOrAbsolute))
{
CacheOption = BitmapCacheOption.OnLoad
};
Image image = new Image();
image.IsEnabled = true;
image.Source = bitmap;
image.Width = bitmap.Width;
image.Height = bitmap.Height;
image.Loaded += this.ImageOnLoaded;
image.Stretch = Stretch.Uniform;
InlineUIContainer container = new InlineUIContainer(image, this.rtbEditor.Selection.Start);
Paragraph paragraph = new Paragraph(container);
var doc = this.rtbEditor.Document;
doc.Blocks.Add(paragraph);
}
}
private void ImageOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var img = sender as Image;
if (img != null)
{
var al = AdornerLayer.GetAdornerLayer(img);
if (al != null)
{
al.Add(new ResizingAdorner(img));
}
}
}
The problem and question is that when the Document is loaded i am unable to figure out how to add the ResizingAdorner to the Images in the document. I am using an attached property to load the document contents, the code below is the part that loads the document:
var stream = new MemoryStream(Encoding.UTF8.GetBytes(GetDocumentXaml(richTextBox)));
var doc = new FlowDocument();
var range = new TextRange(doc.ContentStart, doc.ContentEnd);
range.Load(stream, DataFormats.Xaml);
richTextBox.Document = doc;
Could anyone help with how I can add the ResizingAdorner to any images in the loaded Document please?

So I have worked out a way that I can add the Adorner to the Image in the Document through the SelectionChanged event handler and the following code
var inline = this.rtbEditor.CaretPosition.GetAdjacentElement(LogicalDirection.Forward) as Inline;
if (inline != null)
{
this.AddAdorner(inline.NextInline as InlineUIContainer);
this.AddAdorner(inline.PreviousInline as InlineUIContainer);
}
And this is the AddAdorner method
private void AddAdorner(InlineUIContainer container)
{
if (container != null)
{
var image = container.Child;
if (image != null)
{
var al = AdornerLayer.GetAdornerLayer(image);
if (al != null)
{
var currentAdorners = al.GetAdorners(image);
if (currentAdorners != null)
{
foreach (var adorner in currentAdorners)
{
al.Remove(adorner);
}
}
al.Add(new ResizingAdorner(image));
}
}
}
}
I know this is probably not the most elegant way to achieve this but for me this works so I wanted to post this so that others can choose to use this if appropriate

Related

WPF : how to save stroke collection to bitmap?

I use InkCanvas to implement signature function.
after signed, I can use RenderTargetBitmap class to save the signature-drawing into bitmap.
but RenderTargetBitmap always save InkCanvas itself, means can not save ONLY the signature content.
my question is, how to save StrokeCollection into bitmap?
I think you should use Win2D (Win2D.uwp NuGet package); it is pretty easy.
Here is the code:
async void SaveAsBitmap(object sender, RoutedEventArgs e)
{
//copy from origianl canvas and paste on the new canvas for saving
var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
//check if canvas is not empty
if (strokes.Count == 0) return;
//select all the strokes to be copied to the clipboard
foreach (var stroke in strokes)
{
stroke.Selected = true;
}
inkCanvas.InkPresenter.StrokeContainer.CopySelectedToClipboard();
//paste the strokes to a new InkCanvas and move the strokes to the topper left corner
var newCanvas = new InkCanvas();
newCanvas.InkPresenter.StrokeContainer.PasteFromClipboard(new Point(0, 0));
//using Win2D to save ink as png
CanvasDevice device = CanvasDevice.GetSharedDevice();
CanvasRenderTarget renderTarget = new CanvasRenderTarget(device,
(int)inkCanvas.InkPresenter.StrokeContainer.BoundingRect.Width,
(int)inkCanvas.InkPresenter.StrokeContainer.BoundingRect.Height,
96);
using (var ds = renderTarget.CreateDrawingSession())
{
//ds.Clear(Colors.White); //uncomment this line for a white background
ds.DrawInk(newCanvas.InkPresenter.StrokeContainer.GetStrokes());
}
//save file dialog
var savePicker = new Windows.Storage.Pickers.FileSavePicker()
{
SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary
};
savePicker.FileTypeChoices.Add("Image file", new List<string>() { ".jpeg", ".png" });
savePicker.SuggestedFileName = "mysign.png";
var file = await savePicker.PickSaveFileAsync();
if (file != null)
{
using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await renderTarget.SaveAsync(fileStream, CanvasBitmapFileFormat.Png);
}
}
}

Take a ScreenShot of my Control - Winform

I want to take a screenshot of my control on Winform. I use this function - find in this site :
public void GetImage()
{
var bm = new Bitmap(display1.Width, display1.Height);
display1.DrawToBitmap(bm, display1.ClientRectangle);
bm.Save(#"c:\whatever.gif", ImageFormat.Gif);
}
But when I'm using it, i have a error about 'System.Runtime.InteropServices.ExternalException' in System.Drawing.dll : A generic error occurred in GDI +.
Do you have any idea aboout that ?
Thanks !
----------------------EDIT---------------------------
Ok I have change my function like this :
public void GetImage()
{
var bm = new Bitmap(display1.Width, display1.Height);
display1.DrawToBitmap(bm, display1.ClientRectangle);
var dlg = new SaveFileDialog { DefaultExt = "png", Filter = "Png Files|*.png" };
var res = dlg.ShowDialog();
if (res == DialogResult.OK) bm.Save(dlg.FileName, ImageFormat.Png);
}
and it's works but I have now an empty picture :/
Try the following:
assuming display1 is a Rectangle that bounds the area of the display you wish to capture.
public void GetImage()
{
Rectangle display1 = this.Bounds; // winforms control bounds
// for full screen use "= Screen.GetBounds(Point.Empty);
var bm = new Bitmap(display1.Width, display1.Height);
//display1.DrawToBitmap(bm, display1.ClientRectangle);
Graphics g = Graphics.FromImage(bm);
g.CopyFromScreen(new Point(display1.Left, display1.Top), Point.Empty, display1.Size);
var dlg = new SaveFileDialog { DefaultExt = "png", Filter = "Png Files|*.png" };
var res = dlg.ShowDialog();
if (res == DialogResult.OK) bm.Save(dlg.FileName, ImageFormat.Png);
}

Deleting File which is displayed in picturebox

I am selecting file from openfiledialoge and displaying it in picturebox and its name in textbox when I click on delete button I am getting exception The process cannot access the file because it is being used by another process.
I searched a lot for this exception to get resolved but i didn't fine any of them working, when i tried closing file with imagename which is in textbox i.e the file i am displaying in picturebox ; using IsFileLocked method,this closes and deletes all files of particular directory path ,but how can I delete the only file shown in picturebox,where I am going wrong
public partial class RemoveAds : Form
{
OpenFileDialog ofd = null;
string path = #"C:\Users\Monika\Documents\Visual Studio 2010\Projects\OnlineExam\OnlineExam\Image\"; // this is the path that you are checking.
public RemoveAds()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (System.IO.Directory.Exists(path))
{
ofd = new OpenFileDialog();
ofd.InitialDirectory = path;
DialogResult dr = new DialogResult();
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
Image img = new Bitmap(ofd.FileName);
string imgName = ofd.SafeFileName;
txtImageName.Text = imgName;
pictureBox1.Image = img.GetThumbnailImage(350, 350, null, new IntPtr());
ofd.RestoreDirectory = true;
}
}
else
{
return;
}
}
private void button2_Click(object sender, EventArgs e)
{
//Image img = new Bitmap(ofd.FileName);
string imgName = ofd.SafeFileName;
if (Directory.Exists(path))
{
var directory = new DirectoryInfo(path);
foreach (FileInfo file in directory.GetFiles())
{ if(!IsFileLocked(file))
file.Delete();
}
}
}
public static Boolean IsFileLocked(FileInfo path)
{
FileStream stream = null;
try
{ //Don't change FileAccess to ReadWrite,
//because if a file is in readOnly, it fails.
stream = path.Open ( FileMode.Open, FileAccess.Read, FileShare.None );
}
catch (IOException)
{ //the file is unavailable because it is:
//still being written to or being processed by another thread
//or does not exist (has already been processed)
return true;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
}
Thanks in advance for any help
The (previously) accepted answer to this question is very poor practice. If you read the documentation on System.Drawing.Bitmap, in particular for the overload that creates a bitmap from a file, you will find :
The file remains locked until the Bitmap is disposed.
in your code you create the bitmap and store it in a local variable but you never dispose of it when you are done. This means your image object has gone out of scope but has not released its lock on the image file you are trying to delete. For all objects that implement IDisposable (like Bitmap) you must dispose of them yourself. See this question for example (or search for others - this is a very important concept!).
To correct the problem properly you simply need to dispose of the image when you are done with it :
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
Image img = new Bitmap(ofd.FileName); // create the bitmap
string imgName = ofd.SafeFileName;
txtImageName.Text = imgName;
pictureBox1.Image = img.GetThumbnailImage(350, 350, null, new IntPtr());
ofd.RestoreDirectory = true;
img.Dispose(); // dispose the bitmap object
}
Please do not take the advice in the answer below - you should nearly never need to call GC.Collect and if you need to do it to make things work it should be a very strong signal that you are doing something else wrong.
Also, if you only want to delete the one file (the bitmap you have displayed) your deletion code is wrong and will delete every file in the directory as well (this is just repeating Adel's point). Further, rather than keep a global OpenFileDialog object alive simply to store the file name, I would suggest getting rid of that and saving just the file info :
FileInfo imageFileinfo; //add this
//OpenFileDialog ofd = null; Get rid of this
private void button1_Click(object sender, EventArgs e)
{
if (System.IO.Directory.Exists(path))
{
OpenFileDialog ofd = new OpenFileDialog(); //make ofd local
ofd.InitialDirectory = path;
DialogResult dr = new DialogResult();
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
Image img = new Bitmap(ofd.FileName);
imageFileinfo = new FileInfo(ofd.FileName); // save the file name
string imgName = ofd.SafeFileName;
txtImageName.Text = imgName;
pictureBox1.Image = img.GetThumbnailImage(350, 350, null, new IntPtr());
ofd.RestoreDirectory = true;
img.Dispose();
}
ofd.Dispose(); //don't forget to dispose it!
}
else
{
return;
}
}
Then in your second button handler you can just delete the one file you are interested in.
private void button2_Click(object sender, EventArgs e)
{
if (!IsFileLocked(imageFileinfo))
{
imageFileinfo.Delete();
}
}
I had the same problem : I loaded a file in a PictureBox and when trying to delete it I got the same exception.
This occurred only when the image was displayed.
I tried them all :
picSelectedPicture.Image.Dispose();
picSelectedPicture.Image = null;
picSelectedPicture.ImageLocation = null;
and still got the same exception.
Then I found this on CodeProject : [c#] delete image which is opened in picturebox.
Instead of using PictureBox.Load() it creates an Image from the file and sets it as PictureBox.Image:
...
// Create image from file and display it in the PictureBox
Image image = GetCopyImage(imagePath);
picSelectedPicture.Image = image;
...
private Image GetCopyImage(string path) {
using (Image image = Image.FromFile(path)) {
Bitmap bitmap = new Bitmap(image);
return bitmap;
}
}
No more exceptions when I delete the file.
IMHO, this is the most suitable solution.
EDIT
I forgot to mention that you can safely delete the file immediately after display :
...
// Create image from file and display it in the PictureBox
Image image = GetCopyImage(imagePath);
picSelectedPicture.Image = image;
System.IO.File.Delete(imagePath);
...
use this code
string imgName = ofd.SafeFileName;
if (Directory.Exists(path))
{
var directory = new DirectoryInfo(path);
foreach (FileInfo file in directory.GetFiles())
{
GC.Collect();
GC.WaitForPendingFinalizers();
file.Delete();
}
}
Your button2_Click event handler is cycling through all the files inside your directory & doing the deletes.
You need to change your code like the following:
public partial class RemoveAds : Form
{
OpenFileDialog ofd = null;
string path = #"C:\Users\Monika\Documents\Visual Studio 2010\Projects\OnlineExam\OnlineExam\Image\"; // this is the path that you are checking.
string fullFilePath;
public RemoveAds()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (System.IO.Directory.Exists(path))
{
ofd = new OpenFileDialog();
ofd.InitialDirectory = path;
DialogResult dr = new DialogResult();
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
Image img = new Bitmap(ofd.FileName);
string imgName = ofd.SafeFileName;
txtImageName.Text = imgName;
pictureBox1.Image = img.GetThumbnailImage(350, 350, null, new IntPtr());
fullFilePath = ofd.FilePath;
ofd.RestoreDirectory = true;
}
}
else
{
return;
}
}
private void button2_Click(object sender, EventArgs e)
{
FileInfo file = new FileInfo(fullFilePath);
if(!IsFileLocked(file))
file.Delete();
}
}
public static Boolean IsFileLocked(FileInfo path)
{
FileStream stream = null;
try
{ //Don't change FileAccess to ReadWrite,
//because if a file is in readOnly, it fails.
stream = path.Open ( FileMode.Open, FileAccess.Read, FileShare.None );
}
catch (IOException)
{ //the file is unavailable because it is:
//still being written to or being processed by another thread
//or does not exist (has already been processed)
return true;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
}
By using GetThumnailImage you have to specify the width and height which is static.
Use the Load method instead.
eg: pictureBox1.Load(Path to the image); by using this u will have no problem in deleting the image or the folder before closing the app. no other methods need to be created.
hope this helps

How do I get an image to save after rotation at runtime?

I am working with WPF and I have an application that the user loads an image file into a RichTextBox and they can rotate the image and print it. I am not sure as to why the image after it has been rotated will not print as it is displayed on the screen. Instead it prints the original. I am new to this so any help would be greatly appreciated!
The following is the code for my application. Code when the retrieve file Button is clicked:
private void retrieve_button_Click(object sender, RoutedEventArgs e)
{
//Retrieve the file or image you are looking for
OpenFileDialog of = new OpenFileDialog();
of.Filter = "Formats|*.jpg;*.png;*.bmp;*.gif;*.ico;*.txt|JPG Image|*.jpg|BMP image|*.bmp|PNG image|*.png|GIF Image|*.gif|Icon|*.ico|Text File|*.txt";
var dialogResult = of.ShowDialog();
if (dialogResult == System.Windows.Forms.DialogResult.OK)
{
try
{
System.Windows.Controls.RichTextBox myRTB = new System.Windows.Controls.RichTextBox();
{
Run myRun = new Run();
System.Windows.Controls.Image MyImage = new System.Windows.Controls.Image();
MyImage.Source = new BitmapImage(new Uri(of.FileName, UriKind.RelativeOrAbsolute));
InlineUIContainer MyUI = new InlineUIContainer();
MyUI.Child = MyImage;
rotateright_button.IsEnabled = true;
print_button.IsEnabled = true;
Paragraph paragraph = new Paragraph();
paragraph.Inlines.Add(myRun);
paragraph.Inlines.Add(MyUI);
FlowDocument document = new FlowDocument(paragraph);
richTextBox.Document = document;
}
}
catch (ArgumentException)
{
System.Windows.Forms.MessageBox.Show("Invalid File");
}
}
}
When the rotate right button is clicked the following code is executed:
RotateTransform cwRotateTransform;
private void rotateright_button_Click(object sender, RoutedEventArgs e)
{
richTextBox.LayoutTransform = cwRotateTransform;
if (cwRotateTransform == null)
{
cwRotateTransform = new RotateTransform();
}
if (cwRotateTransform.Angle == 360)
{
cwRotateTransform.Angle = 0;
}
else
{
cwRotateTransform.Angle += 90;
}
}
After the Image has been loaded and rotated the user can use the following code to print:
private void InvokePrint(object sender, RoutedEventArgs e)
{
System.Windows.Controls.PrintDialog printDialog = new System.Windows.Controls.PrintDialog();
if ((bool)printDialog.ShowDialog().GetValueOrDefault())
{
FlowDocument flowDocument = new FlowDocument();
flowDocument = richTextBox.Document;
flowDocument.ColumnWidth = printDialog.PrintableAreaWidth;
flowDocument.PagePadding = new Thickness(65);
IDocumentPaginatorSource iDocPag = flowDocument;
printDialog.PrintDocument(iDocPag.DocumentPaginator, "Print Document");
}
}
Try this (substitute yourImageControl in the first line, specify which RotateFlipType you want and be sure to reference the System.Drawing dll):
System.Drawing.Bitmap bitmap = BitmapSourceToBitmap((BitmapSource)YourImageControl.Source);
bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate90FlipNone);
public static System.Drawing.Bitmap BitmapSourceToBitmap(BitmapSource bitmapsource)
{
System.Drawing.Bitmap bitmap;
using (MemoryStream outStream = new MemoryStream())
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bitmapsource));
enc.Save(outStream);
bitmap = new System.Drawing.Bitmap(outStream);
}
return bitmap;
}
Another option for conversion...
P.S. You would get a better answer in less time if you posted some code and told us more about what you have tried.

How to set the Image property of a PictureBox Control by using OpenFileDialog? C# winforms

How to set the Image property of a PictureBox Control by using OpenFileDialog?
Set PictureBox.Image using Image.FromFile
OpenFileDialog _openFileDialog = new OpenFileDialog();
_openFileDialog.FileOk += OpenFileDialogFileOk;
_openFileDialog.CheckFileExists = true;
_openFileDialog.CheckPathExists = true;
_openFileDialog.ShowDialog();
void OpenFileDialogFileOk(object sender, CancelEventArgs e)
{
var imageInformation = new FileInfo(_openFileDialog.FileName);
var sizeInBytes = imageInformation.Length;
myPictureBox.Image = Image.FromFile(_openFileDialog.FileName);
}

Categories