I have a icon which has a few different sizes (16px, 32px, 64px). I am calling ToBitmap() on it, but it is always returning the 32px image. How do I retrieve the 64px one?
Does this help?
Icon sizedIcon = new Icon(Resources.ResourceIcon, new Size(64,64));
For anyone else stumbling upon the same problem, I've found a nice little solution.
Image img = new Icon(Properties.Resources.myIcon, width, height).ToBitmap()
This is a fairly painful limitation in the ResourceManager class. Its GetObject() method doesn't provide a way to pass extra arguments that would allow selecting the returned icon by size. A workaround is to add the icon to the project instead. Use Project + Add Existing Item, select your .ico file. Select the added icon and change the Build Action property to "Embedded Resource".
You can now retrieve the desired icon with code like this:
public static Icon GetIconFromEmbeddedResource(string name, Size size) {
var asm = System.Reflection.Assembly.GetExecutingAssembly();
var rnames = asm.GetManifestResourceNames();
var tofind = "." + name + ".ICO";
foreach (string rname in rnames) {
if (rname.EndsWith(tofind, StringComparison.CurrentCultureIgnoreCase)) {
using (var stream = asm.GetManifestResourceStream(rname)) {
return new Icon(stream, size);
}
}
}
throw new ArgumentException("Icon not found");
}
Sample usage:
var icon1 = GetIconFromEmbeddedResource("ARW04LT", new Size(16, 16));
var icon2 = GetIconFromEmbeddedResource("ARW04LT", new Size(32, 32));
Beware one possible failure mode: this code assumes that the icon was added to the same assembly that contains the method.
The following sets the icon size for all the buttons in the toolbar.
It relies on the resource name being stored in the button tag.
public void SetButtons(object toolstrip, int IconWidth, int IconHeight)
{
var ts = (ToolStrip) toolstrip;
var size = new System.Drawing.Size();
size.Height = IconSize;
size.Width = IconSize;
foreach (ToolStripButton tsBtn in ts.Items)
{
tsBtn.ImageScaling = ToolStripItemImageScaling.None;
var resourcename = (String) tsBtn.Tag;
if (resourcename != null)
{
var myIcon = (Icon) Properties.Resources.ResourceManager.GetObject(resourcename);
var newIcon = new Icon(myIcon, IconWidth, IconHeight);
tsBtn.Image = newIcon.ToBitmap();
}
}
}
The size is determined when you first create the Icon instance, so you need to specify which size you want to use when you create it, using one of the Icon constructors that take a Size parameter.
internal static class IconHelper {
public static Icon GetSize(this Icon icon, int width, int height) {
return icon.GetSize(new Size(width, height));
}
public static Icon GetSize(this Icon icon, Size size) {
using(var mem = new MemoryStream()) {
icon.Save(mem);
mem.Position = 0;
return new Icon(mem, size);
}
}
}
There is no built-in method in the .Net framework that does this.
Instead, you can use this library.
Related
I'm working with Xamarin in the part of Xamarin.Forms i need to convert a file ("image.png") to a Bitmap because when project run its enter in "break mode" and show me this message "Java.Lang.IllegalArgumentException: 'Failed to decode image. The provided image must be a Bitmap.'". So i tried to convert the file in many ways like:
1_ Using methods from System.Drawing.Bitmap but it's show this exception "This operation is not supported in this platform"
2_ Cannot use Android.Graphics because i'm working in xamarin.forms not in xamarin.android.
3_ I tried to convert the "image.png" into a base64 or byte[] array and then into a bitmap but the problem is the same like in first problem because to convert from byte[] array or base64 to a Bitmap i have use methods from System.Drawing.Bitmap.
4_ I tried using the library SkiaSharp but i don't have success because i don't found so much information about how to convert .png to SKBitmap and then convert SKBitmap to Bitmap (even i don't know if this is possible).
5_ Finally i converted "image.png" to a "image.bmp" with an app and use the file .bmp in my project but it doesn't work too.
the "image.png" i have to save in a string variable, well that's the idea.
If you have a solution with SkiaSharp o whatever i will glad
EDIT
here is a part of my code, i just save in a variable the image
Icon = "pin_blue.png";
//i can't use a path because in xamarin you have many size from the same
//image for the different size of the screen
EDIT 2 This is my method to show the pins in google maps
private void ShowPins(List<PointsOfInterest> resultPointsOfInterest)
{
if (resultPointsOfInterest != null && resultPointsOfInterest.Any())
{
var location = Geolocation.GetLastKnownLocationAsync();
PointsOfInterest position = new PointsOfInterest();
if (location != null)
{
position.ccsm0166latitud = location.Result.Latitude;
position.ccsm0166longitud = location.Result.Longitude;
}
else {
position = resultPointsOfInterest.First();
}
//Distance = Distance.FromKilometers(new Random().Next(23,35));
Distance = Distance.FromKilometers(3);
Position = new Position(position.ccsm0166latitud, position.ccsm0166longitud);
PinsFiltered= Pins = new List<PinCustom>(resultPointsOfInterest.Select(
x => new PinCustom()
{
Position =
new Position(x.ccsm0166latitud, x.ccsm0166longitud),
Address = x.ccsm0166direccion,
Label = x.ccsm0166nombre,
Type = PinType.Place,
TypePointOfInterest = x.ccsm0166tipositio,
IconBM = Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branch ? PinCustom.ConvertToBitmap("pin_blue.png") :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branch ? "pin_blue.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branchWithExtendedHours ? "pin_black.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branchWithExtendedHours2 ? "pin_black.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.branchWithExtendedHours3 ? "pin_black.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.selfServiceTerminal ? "pin_green.png" :
Int32.Parse(x.ccsm0166tipositio) == (int)PointOfInterestType.atmServBox ? "pin_yellow.png" : string.Empty
}).ToList());
}
else
{
Pins = new List<PinCustom>();
}
}
This is the class Pin where i save the image
public class PinCustom : Pin
{
public string TypePointOfInterest { get; set; }
public string Icon { get; set; }
public Bitmap { get; set; }
//Here i create this variable to save a bitmap but i don't know if i do the things well
}
this is the icon .png i want to show in googlemaps
Pin Blue Image
To open your file and convert it to byte array:
string directory = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, Android.OS.Environment.DirectoryDownloads);
// or your image directory, just replace it
var stream = File.OpenWrite(Path.Combine(directory, "image.png"));
byte[] buff = ConvertStreamToByteArray(stream);
stream.Close();
public static byte[] ConvertStreamToByteArray(Stream stream)
{
byte[] byteArray = new byte[16 * 1024];
using (MemoryStream mStream = new MemoryStream())
{
int bit;
while ((bit = stream.Read(byteArray, 0, byteArray.Length)) > 0)
{
mStream.Write(byteArray, 0, bit);
}
return mStream.ToArray();
}
}
then, to pass this byte array to SKBitmap:
SKBitmap bmp = SKBitmap.Decode(buff);
EDIT:
If you want to try without ConvertStreamToByteArray():
byte[] buff = null;
stream.Write(buff, 0, buff.Length);
This Write will depend of the type of your stream. In this example, I'm using FileStream.
EDIT 2:
string resourceID = "image.png";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
resourceBitmap = SKBitmap.Decode(stream);
SKBitmap bmp = SKImage.FromBitmap(resourceBitmap);
}
and then you can use this bitmap to draw and fill your SKCanvas:
canvas.DrawBitmap(bmp, 0, 0);
Use ffmpeg
command for this: ffmpeg -i image.png image.bmp
i have 5 types of pins (pins are the image .png) when i put the pins
in format .png
if you want to custom the pins in your map, you can simply use Custom Renderers to achieve this.
The icon used to represent a marker can be customized by calling the MarkerOptions.SetIcon method. This can be accomplished by overriding the CreateMarker method, which is invoked for each Pin that's added to the map:
protected override MarkerOptions CreateMarker(Pin pin)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
return marker;
}
If you want to display different icons, you can refer to the following code:
protected override MarkerOptions CreateMarker(Pin pin)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
var customPin = (Element as CustomMap).CustomPins.FirstOrDefault(p => p.Position == pin.Position);
if (customPin != null)
{
if (customPin.IconType == "corporate")
{
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.corporate));
}
else if (customPin.IconType == "pickup")
{
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.bus));
}
}
return marker;
}
For more, check:Customizing the Marker.
Actually I am in trouble. My project is to create a desktop with shortcuts. The desktop is ready, it is a picturebox as background. And now the shortcuts.
Through a dialog, I will create a shortcut on the picturebox (desktop). Example:
Here is my desktop:
https://pasteboard.co/IHSdXvX.png
Here is my dialog:
https://pasteboard.co/IHSefrz.png
First textbox is the path to the file. Second textbox is the name of the shortcut.
And here is the result:
https://pasteboard.co/IHSeM7Y.png
In the upper left corner is a new green shortcut. It is a picturebox, which was created by this code:
Icon ico = Icon.ExtractAssociatedIcon(textBox1.Text);
var picture = new PictureBox
{
Name = textBox2.Text,
Size = new Size(48, 46),
Location = new Point(100, 100),
Image = ico.ToBitmap(),
SizeMode = PictureBoxSizeMode.Zoom,
};
Form1Singleton.FormVerweis.pictureBox1.Controls.Add(picture);
So, my question is:
How can I save the new created picturebox (the green icon upper left corner) and load it back to the same position, and the same click-Event to start the .exe behind it (exe path in textbox 1 in the dialog before) when the application starts. Information about the picturebox to save: Location, Icon, .exe Path.
Thanks for your help and I am really happy about code-examples.
Joshua
You will need to create a class that stores all the information you need to create an icon:
[Serializable]
public class IconDefinition
{
public string Name { get; set; }
public string Path { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
Then you have some method that actually creates your icon based on these values:
void createIcon(IconDefinition iconDefinition)
{
PictureBox pictureBox = new PictureBox()
{
Name = iconDefinition.Name,
Size = new Size(iconDefinition.Width, iconDefinition.Height),
Location = new Point(iconDefinition.X, iconDefinition.Y),
Image = Icon.ExtractAssociatedIcon(iconDefinition.Path).ToBitmap(),
SizeMode = PictureBoxSizeMode.Zoom
};
pictureBox.Click += myClickHandler;
Form1Singleton.FormVerweis.pictureBox1.Controls.Add(pictureBox);
}
Next you have your methods to serialize and deserialize these. Since you probably want to store all your icons in the same file for persistence you would use a List for serialization.
static void saveIcons(List<IconDefinition> icons)
{
using (Stream stream = new FileStream(#".\icons.bin", FileMode.Create, FileAccess.Read))
new BinaryFormatter().Serialize(stream, icons);
}
static List<IconDefinition> loadIcons()
{
using (Stream stream = new FileStream(#".\icons.bin", FileMode.Open, FileAccess.Read))
return (List<IconDefinition>)new BinaryFormatter().Deserialize(stream);
}
These will provide you functionality to write a List<IconDefinition> to a file, or restore it from a file.
Now you will have to have a place somewhere to put all your icons:
List<IconDefinition> allIcons = new List<IconDefinition>();
When manually creating the icon (as you do already):
IconDefinition iconDefinition = new IconDefinition()
{
Path = textBox1.Text,
Name = textBox2.Text,
X = 100,
Y = 100,
Width = 48,
Height = 46
};
allIcons.Add(iconDefinition);
createIcon(iconDefinition);
When you want to save all your icons:
saveIcons(allIcons);
And when your application starts to restore them:
// When application starts you can load the icons
allIcons = loadIcons();
// then restore them all
foreach (IconDefinition definition in allIcons)
createIcon(definition);
Hope that helps.
I have a icon which has a few different sizes (16px, 32px, 64px). I am calling ToBitmap() on it, but it is always returning the 32px image. How do I retrieve the 64px one?
Does this help?
Icon sizedIcon = new Icon(Resources.ResourceIcon, new Size(64,64));
For anyone else stumbling upon the same problem, I've found a nice little solution.
Image img = new Icon(Properties.Resources.myIcon, width, height).ToBitmap()
This is a fairly painful limitation in the ResourceManager class. Its GetObject() method doesn't provide a way to pass extra arguments that would allow selecting the returned icon by size. A workaround is to add the icon to the project instead. Use Project + Add Existing Item, select your .ico file. Select the added icon and change the Build Action property to "Embedded Resource".
You can now retrieve the desired icon with code like this:
public static Icon GetIconFromEmbeddedResource(string name, Size size) {
var asm = System.Reflection.Assembly.GetExecutingAssembly();
var rnames = asm.GetManifestResourceNames();
var tofind = "." + name + ".ICO";
foreach (string rname in rnames) {
if (rname.EndsWith(tofind, StringComparison.CurrentCultureIgnoreCase)) {
using (var stream = asm.GetManifestResourceStream(rname)) {
return new Icon(stream, size);
}
}
}
throw new ArgumentException("Icon not found");
}
Sample usage:
var icon1 = GetIconFromEmbeddedResource("ARW04LT", new Size(16, 16));
var icon2 = GetIconFromEmbeddedResource("ARW04LT", new Size(32, 32));
Beware one possible failure mode: this code assumes that the icon was added to the same assembly that contains the method.
The following sets the icon size for all the buttons in the toolbar.
It relies on the resource name being stored in the button tag.
public void SetButtons(object toolstrip, int IconWidth, int IconHeight)
{
var ts = (ToolStrip) toolstrip;
var size = new System.Drawing.Size();
size.Height = IconSize;
size.Width = IconSize;
foreach (ToolStripButton tsBtn in ts.Items)
{
tsBtn.ImageScaling = ToolStripItemImageScaling.None;
var resourcename = (String) tsBtn.Tag;
if (resourcename != null)
{
var myIcon = (Icon) Properties.Resources.ResourceManager.GetObject(resourcename);
var newIcon = new Icon(myIcon, IconWidth, IconHeight);
tsBtn.Image = newIcon.ToBitmap();
}
}
}
The size is determined when you first create the Icon instance, so you need to specify which size you want to use when you create it, using one of the Icon constructors that take a Size parameter.
internal static class IconHelper {
public static Icon GetSize(this Icon icon, int width, int height) {
return icon.GetSize(new Size(width, height));
}
public static Icon GetSize(this Icon icon, Size size) {
using(var mem = new MemoryStream()) {
icon.Save(mem);
mem.Position = 0;
return new Icon(mem, size);
}
}
}
There is no built-in method in the .Net framework that does this.
Instead, you can use this library.
Within the property window of a JPEG image, there is a tab called 'Summary'. Within this tab, there is a field called 'Comments' I would like to write some c# code which will add a given string to this field e.g "This is a photo".
Does some kind soul out there know how to do this?
Many thanks.
Based on other answers I wrote the following class which allows various metadata manipulations. You use it like this:
var jpeg = new JpegMetadataAdapter(pathToJpeg);
jpeg.Metadata.Comment = "Some comments";
jpeg.Metadata.Title = "A title";
jpeg.Save(); // Saves the jpeg in-place
jpeg.SaveAs(someNewPath); // Saves with a new path
The differences between my solution and the others are not large. Principally I have refactored this to be cleaner. I also use the higher level properties of BitmapMetadata, rather than the SetQuery method.
Here is the full code, which is licensed under the MIT licence. You will need to add references to PresentationCore, WindowsBase, and System.Xaml.
public class JpegMetadataAdapter
{
private readonly string path;
private BitmapFrame frame;
public readonly BitmapMetadata Metadata;
public JpegMetadataAdapter(string path)
{
this.path = path;
frame = getBitmapFrame(path);
Metadata = (BitmapMetadata)frame.Metadata.Clone();
}
public void Save()
{
SaveAs(path);
}
public void SaveAs(string path)
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(frame, frame.Thumbnail, Metadata, frame.ColorContexts));
using (Stream stream = File.Open(path, FileMode.Create, FileAccess.ReadWrite))
{
encoder.Save(stream);
}
}
private BitmapFrame getBitmapFrame(string path)
{
BitmapDecoder decoder = null;
using (Stream stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
decoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
}
return decoder.Frames[0];
}
}
The following code solves my problem and adds comments to a given JPEG image:
public void addImageComment(string imageFlePath, string comments)
{
string jpegDirectory = Path.GetDirectoryName(imageFlePath);
string jpegFileName = Path.GetFileNameWithoutExtension(imageFlePath);
BitmapDecoder decoder = null;
BitmapFrame bitmapFrame = null;
BitmapMetadata metadata = null;
FileInfo originalImage = new FileInfo(imageFlePath);
if (File.Exists(imageFlePath))
{
// load the jpg file with a JpegBitmapDecoder
using (Stream jpegStreamIn = File.Open(imageFlePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
decoder = new JpegBitmapDecoder(jpegStreamIn, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
}
bitmapFrame = decoder.Frames[0];
metadata = (BitmapMetadata)bitmapFrame.Metadata;
if (bitmapFrame != null)
{
BitmapMetadata metaData = (BitmapMetadata)bitmapFrame.Metadata.Clone();
if (metaData != null)
{
// modify the metadata
metaData.SetQuery("/app1/ifd/exif:{uint=40092}", comments);
// get an encoder to create a new jpg file with the new metadata.
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapFrame, bitmapFrame.Thumbnail, metaData, bitmapFrame.ColorContexts));
//string jpegNewFileName = Path.Combine(jpegDirectory, "JpegTemp.jpg");
// Delete the original
originalImage.Delete();
// Save the new image
using (Stream jpegStreamOut = File.Open(imageFlePath, FileMode.CreateNew, FileAccess.ReadWrite))
{
encoder.Save(jpegStreamOut);
}
}
}
}
}
This is essentially a lightly modified version of the code found under the link which Konamiman kindly supplied.
Please be aware that to make this work you will need to add .NET references to PresentationCore and WindowsBase. If using Visual Studio 2008, this can be achieved via the following:
Right click on your project in the Solution Explorer
From the drop down list, select Add 'Reference...'
From the new box which opens, select the '.NET' tab
Scroll to the two references mentioned above and on each, click ok
Many thanks to both danbystrom and Konamiman for your help in this matter. I really appreciate the quick response.
The easy part:
Add this property item:
var data = System.Text.Encoding.UTF8.GetBytes( "Some comments" );
PropertyItem pi;
*** create an empty PropertyItem here
pi.Type = 2;
pi.Id = 37510;
pi.Len = data.Length;
pi.Value = data;
To the Image's PropertItems collection.
The somewhat more cumbersome part:
How do you create a new PropertyItem, since it has no public constructor?
The common "trick" is to have an empty image lying around from which you can steal a PropertyItem. sigh
Thanks to the answers here, I've coded a solution to set a comment using memory only:
public static Image SetImageComment(Image image, string comment) {
using (var memStream = new MemoryStream()) {
image.Save(memStream, ImageFormat.Jpeg);
memStream.Position = 0;
var decoder = new JpegBitmapDecoder(memStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
BitmapMetadata metadata;
if (decoder.Metadata == null) {
metadata = new BitmapMetadata("jpg");
} else {
metadata = decoder.Metadata;
}
metadata.Comment = comment;
var bitmapFrame = decoder.Frames[0];
BitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapFrame, bitmapFrame.Thumbnail, metadata, bitmapFrame.ColorContexts));
var imageStream = new MemoryStream();
encoder.Save(imageStream);
imageStream.Position = 0;
image.Dispose();
image = null;
return Image.FromStream(imageStream);
}
}
Don't forget to dispose the image that is returned by this method. (For example after saving the image to a file)
Thanks to the previous tips I was able to put the following together. I've tested it and it seems to work. One of the biggest stumbling blocks was determining the Id needed for the field you want to assign.
string fileName = "c:/SomeImage.jpg";
// Retrieve the Image
System.Drawing.Image originalImage = System.Drawing.Image.FromFile(fileName);
// Get the list of existing PropertyItems. i.e. the metadata
PropertyItem[] properties = originalImage.PropertyItems;
// Create a bitmap image to assign attributes and do whatever else..
Bitmap bmpImage = new Bitmap((Bitmap)originalImage);
// Don't need this anymore
originalImage.Dispose();
// Get / setup a PropertyItem
PropertyItem item = properties[0]; // We have to copy an existing one since no constructor exists
// This will assign "Joe Doe" to the "Authors" metadata field
string sTmp = "Joe DoeX"; // The X will be replaced with a null. String must be null terminated.
var itemData = System.Text.Encoding.UTF8.GetBytes(sTmp);
itemData[itemData.Length - 1] = 0;// Strings must be null terminated or they will run together
item.Type = 2; //String (ASCII)
item.Id = 315; // Author(s), 315 is mapped to the "Authors" field
item.Len = itemData.Length; // Number of items in the byte array
item.Value = itemData; // The byte array
bmpImage.SetPropertyItem(item); // Assign / add to the bitmap
// This will assign "MyApplication" to the "Program Name" field
sTmp = "MyApplicationX";
itemData = System.Text.Encoding.UTF8.GetBytes(sTmp);
itemData[itemData.Length - 1] = 0; // Strings must be null terminated or they will run together
item.Type = 2; //String (ASCII)
item.Id = 305; // Program Name, 305 is mapped to the "Program Name" field
item.Len = itemData.Length;
item.Value = itemData;
bmpImage.SetPropertyItem(item);
// Save the image
bmpImage.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);
//Clean up
bmpImage.Dispose();
A variant on Peter Kistler's solution to set Title, Subject and Comment. I found I had to create items as Unicode byte array (type 1) and the IDs for Title, Subject and Comment are the same as for EXIF XPTitle, XPSubject and XP Comment. sFileOut can be the same as sFile.
public static void SetWindowsTags2(string sFile, string sFileOut, string Title = "", string Subject = "", string Comment = "", bool bShowError = false)
{
try
{
// Retrieve the Image
System.Drawing.Image originalImage = System.Drawing.Image.FromFile(sFile);
// Get the list of existing PropertyItems. i.e. the metadata
PropertyItem[] properties = originalImage.PropertyItems;
/*foreach (PropertyItem propItem in properties)
{
string sTag = System.Text.Encoding.UTF8.GetString(propItem.Value);
string sItem = sTag.Replace("\0", string.Empty);
Debug.Print(propItem.Id.ToString() + ", " + propItem.Type + ", " + sItem);
}*/
// Create a bitmap image to assign attributes and do whatever else..
Bitmap bmpImage = new Bitmap((Bitmap)originalImage);
// Don't need this anymore
originalImage.Dispose();
// Get / setup a PropertyItem
PropertyItem item = properties[0]; // We have to copy an existing one since no constructor exists
var itemData = System.Text.Encoding.Unicode.GetBytes(Title);
itemData[itemData.Length - 1] = 0;// Strings must be null terminated or they will run together
item.Type = 1; //Unicode Byte Array
item.Id = 40091; // Title ID
item.Len = itemData.Length; // Number of items in the byte array
item.Value = itemData; // The byte array
bmpImage.SetPropertyItem(item); // Assign / add to the bitmap
itemData = System.Text.Encoding.Unicode.GetBytes(Subject);
itemData[itemData.Length - 1] = 0; // Strings must be null terminated or they will run together
item.Type = 1; //Unicode Byte Array
item.Id = 40095; // subject
item.Len = itemData.Length;
item.Value = itemData;
bmpImage.SetPropertyItem(item); // Assign / add to the bitmap
itemData = System.Text.Encoding.Unicode.GetBytes(Comment);
itemData[itemData.Length - 1] = 0; // Strings must be null terminated or they will run together
item.Type = 1; ////Unicode Byte Array
item.Id = 40092; // comment
item.Len = itemData.Length;
item.Value = itemData;
bmpImage.SetPropertyItem(item); // Assign / add to the bitmap
// Save the image
bmpImage.Save(sFileOut, System.Drawing.Imaging.ImageFormat.Jpeg);
//Clean up
bmpImage.Dispose();
}
catch (Exception Ex)
{
}
}
I'm using a class I've derived from DocumentPaginator (see below) to print simple (text only) reports from a WPF application. I've got it so that everything prints correctly, But how do I get it to do a print preview before printing? I have a feeling I need to use a DocumentViewer but I can't figure out how.
Here's my Paginator Class:
public class RowPaginator : DocumentPaginator
{
private int rows;
private Size pageSize;
private int rowsPerPage;
public RowPaginator(int rows)
{
this.rows = rows;
}
public override DocumentPage GetPage(int pageNumber)
{
int currentRow = rowsPerPage * pageNumber;
int rowsToPrint = Math.Min(rowsPerPage, rows - (rowsPerPage * pageNumber - 1));
var page = new PageElementRenderer(pageNumber + 1, PageCount, currentRow, rowsToPrint)
{
Width = PageSize.Width,
Height = PageSize.Height
};
page.Measure(PageSize);
page.Arrange(new Rect(new Point(0, 0), PageSize));
return new DocumentPage(page);
}
public override bool IsPageCountValid { get { return true; } }
public override int PageCount { get { return (int)Math.Ceiling(this.rows / (double)this.rowsPerPage); } }
public override Size PageSize
{
get { return this.pageSize; }
set
{
this.pageSize = value;
this.rowsPerPage = PageElementRenderer.RowsPerPage(this.pageSize.Height);
if (rowsPerPage <= 0)
throw new InvalidOperationException("Page can't fit any rows!");
}
}
public override IDocumentPaginatorSource Source { get { return null; } }
}
The PageElementRenderer is just a simple UserControl that displays the data (at the moment just a list of rows).
Here's how I use my Row Paginator
PrintDialog dialog = new PrintDialog();
if (dialog.ShowDialog() == true)
{
var paginator = new RowPaginator(rowsToPrint) { PageSize = new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight) };
dialog.PrintDocument(paginator, "Rows Document");
}
Sorry for the code dump, but I didn't want to miss something relevant.
So I got it working after reading Pro WPF in C# 2008 (Page 726).
Basically the DocumentViewer class needs an XPS file to present a print preview of it. So I do the following:
PrintDialog dialog = new PrintDialog();
var paginator = new RowPaginator(rowsToPrint) { PageSize = new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight) };
string tempFileName = System.IO.Path.GetTempFileName();
//GetTempFileName creates a file, the XpsDocument throws an exception if the file already
//exists, so delete it. Possible race condition if someone else calls GetTempFileName
File.Delete(tempFileName);
using (XpsDocument xpsDocument = new XpsDocument(tempFileName, FileAccess.ReadWrite))
{
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
writer.Write(paginator);
PrintPreview previewWindow = new PrintPreview
{
Owner = this,
Document = xpsDocument.GetFixedDocumentSequence()
};
previewWindow.ShowDialog();
}
I'm creating the print dialog to get the default page size. There's probably a better way to do this.
XpsDocument is in ReachFramework.dll (Namespace System.Windows.Xps.Packaging);
Here's the PrintPreview Window.
<Window x:Class="WPFPrintTest.PrintPreview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="previewWindow"
Title="PrintPreview" Height="800" Width="800">
<Grid>
<DocumentViewer Name="viewer"
Document="{Binding ElementName=previewWindow, Path=Document}" />
</Grid>
</Window>
The code behind just has a Document property like so:
public IDocumentPaginatorSource Document
{
get { return viewer.Document; }
set { viewer.Document = value; }
}
The following code uses a MemoryStream to do a print preview.
MemoryStream stream = new MemoryStream();
Package package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite);
var uri = new Uri(#"memorystream://myXps.xps");
PackageStore.AddPackage(uri, package);
var xpsDoc = new XpsDocument(package);
xpsDoc.Uri = uri;
XpsDocument.CreateXpsDocumentWriter(xpsDoc).Write(paginator);
documentViewer.Document = xpsDoc.GetFixedDocumentSequence();
Remember to use close() and remove package from PackageStore when the print preview is closed.
WPF doesn't come with any built-in Print Preview functionality, if you want to do a print preview, you're going to have to construct it yourself. Fortunately, it's shouldn't be that difficult.
You've already got the pagination code, which creates your DocumentPage objects. These objects contain a Visual, which you can go ahead and display in your UI.
What you'll end up doing, is paginating your entire document, collecting all the DocumentPage objects, then displaying their visuals inside of a scrolling StackPanel or something similar. These are the same DocumentPage objects that you can then send to the printer.
XpsDocument doc = new XpsDocument("filename.xps", FileAccess.Read);
docViewer.Document = doc.GetFixedDocumentSequence();
doc.Close();
The WinForm code for print preview is:
PrintPreviewDialog PrintPreviewDialog = new PrintPreviewDialog();
PrintPreviewDialog.Document = PrintDocument;
PrintPreviewDialog.ShowDialog();
P.s.: The original poster has commented that this is the wrong answer for an WPF application.
I dont think there is a built in way of doing this
Here is how I got it working in NHaml
var documentViewHostDialog = new DocumentDialog();
documentViewHostDialog.LoadDocument(load);
documentViewHostDialog.ShowDialog();
Where "load" is either a FlowDocument or IDocumentPaginatorSource
and here is the DocumentDialog
http://code.google.com/p/nhaml/source/browse/trunk/src/NHaml.Xps/DocumentDialog.xaml
http://code.google.com/p/nhaml/source/browse/trunk/src/NHaml.Xps/DocumentDialog.xaml.cs
Hope it works for your case.