This question already has answers here:
How to Print Preview when using a DocumentPaginator to print?
(6 answers)
Closed 4 years ago.
I am working with printing in WPF.
I implement a class which inherit from DocumentPaginator class
public class ReportPaginator : DocumentPaginator
{
private double pageUpperLimit;
private double pageDownLimit;
private Model.Report printedReport;
private int pageCount;
public override IDocumentPaginatorSource Source
{
get
{
return null;
}
}
public override bool IsPageCountValid
{
get { return true; }
}
public override int PageCount
{
get { return pageCount; }
}
public override Size PageSize
{
get
{
return new Size(printedReport.PageSize.Width, printedReport.PageSize.Height);
}
set
{
// validate the value.
if (value != null)
{
throw new ArgumentException("page size can not be null");
}
// TODO: here we have to validate if the page is A3, A4, A5. this is to set the SizeName.
// if you did not set the sizeName here we will get an exception.
printedReport.PageSize = new Model.PageSize { Height = value.Height, Width = value.Width };
CalculatesPage();
// if we have to set the PageSize (I do not think so), do not forget to call the PaginateData method.
}
}
public ReportPaginator( Model.Report report)
{
printedReport = report;
CalculatesPage();
}
public override DocumentPage GetPage(int pageNumber)
{
// validate the argument.
if (pageNumber < 0)
{
throw new ArgumentException("pageNumber parameter could not be negative number", "pageNumber");
}
// if the argument is outside of the available pages, return
if (pageNumber > pageCount - 1)
{
return DocumentPage.Missing;
}
// specify the start pixel and end pixel of the page according to the height of the report.
pageUpperLimit = pageNumber * PageSize.Height;
pageDownLimit = (pageNumber + 1) * PageSize.Height;
// create DrawingVisual for this DocumentPage
DrawingVisual visual = new DrawingVisual();
// get the DrawingContext for this DrawingVisual for this page.
using (DrawingContext pageContext = visual.RenderOpen())
{
// drawing operations for elements and sections go here.
// we will use the for loop instead of the foreach loop to enumerate the sections
// because we will need the know in which section we draw.
for (int sectionIndex = 0; sectionIndex < printedReport.Sections.Count; sectionIndex++)
{
PrintSection(pageContext, sectionIndex);
foreach (Model.BaseReportControl control in printedReport.Sections[sectionIndex].Controls)
{
PrintControl(pageContext, sectionIndex, control);
}
}
} // close the DrawingContext.
return new DocumentPage(visual);
}
}
there is more helper methods to do the jobs.
I want to create FixedDocument object to return it from the Source property
using DocumentPage object that returned from GetPage Method.
I need a FixedDocument to be returned from the source Property
Because I want to make a preview to the File before printing
ReportPreviewerWindow window = new ReportPreviewerWindow();
window.previewer.Document = new ReportPaginator(printedSurface.ModelReport).Source;
window.ShowDialog();
the previewer is DocumentViewer Object exists in the ReportPreviewerWindow
<Window x:Class="TanmiaGrp.Report.Designer.ReportPreviewerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Report Previewer" >
<Grid>
<DocumentViewer x:Name="previewer"/>
</Grid>
</Window>
After I asked this question I found something worked very correctly for me.
if you have problem like the described above please refer this link
How to Print Preview when using a DocumentPaginator to print?
Related
Hello I'm writing a WPF program that gets has thumbnails inside a ThumbnailViewer. I want to generate the Thumbnails first, then asynchronously generate the images for each thumbnail.
I can't include everything but I think this is whats relevant
Method to generate the thumbnails.
public async void GenerateThumbnails()
{
// In short there is 120 thumbnails I will load.
string path = #"C:\....\...\...png";
int pageCount = 120;
SetThumbnails(path, pageCount);
await Task.Run(() => GetImages(path, pageCount);
}
SetThumbnails(string path, int pageCount)
{
for(int i = 1; i <= pageCount; i ++)
{
// Sets the pageNumber of the current thumbnail
var thumb = new Thumbnail(i.ToString());
// Add the current thumb to my thumbs which is
// binded to the ui
this._viewModel.thumbs.Add(thumb);
}
}
GetImages(string path, int pageCount)
{
for(int i = 1; i <= pageCount; i ++)
{
Dispatcher.Invoke(() =>
{
var uri = new Uri(path);
var bitmap = new BitmapImage(uri);
this._viewModel.Thumbs[i - 1].img.Source = bitmap;
});
}
}
When I run the code above it works just as if I never add async/await/task to the code. Am I missing something? Again What I want is for the ui to stay open and the thumbnail images get populated as the GetImage runs. So I should see them one at a time.
UPDATE:
Thanks to #Peregrine for pointing me in the right direction. I made my UI with custom user controls using the MVVM pattern. In his answer he used it and suggested that I use my viewModel. So what I did is I add a string property to my viewModel and made an async method that loop though all the thumbnails and set my string property to the BitmapImage and databound my UI to that property. So anytime it would asynchronously update the property the UI would also update.
The Task that runs GetImages does virtually nothing but Dispatcher.Invoke, i.e. more or less all your code runs in the UI thread.
Change it so that the BitmapImage is created outside the UI thread, then freeze it to make it cross-thread accessible:
private void GetImages(string path, int pageCount)
{
for (int i = 0; i < pageCount; i++)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
bitmap.Freeze();
Dispatcher.Invoke(() => this._viewModel.Thumbs[i].img.Source = bitmap);
}
}
You should also avoid any async void method, excpet when it is an event handler. Change it as shown below, and await it when you call it:
public async Task GenerateThumbnails()
{
...
await Task.Run(() => GetImages(path, pageCount));
}
or just:
public Task GenerateThumbnails()
{
...
return Task.Run(() => GetImages(path, pageCount));
}
An alternative that altogether avoids async/await is a view model with an ImageSource property whose getter is called asynchronously by specifying IsAsync on the Binding:
<Image Source="{Binding Image, IsAsync=True}"/>
with a view model like this:
public class ThumbnailViewModel
{
public ThumbnailViewModel(string path)
{
Path = path;
}
public string Path { get; }
private BitmapImage îmage;
public BitmapImage Image
{
get
{
if (îmage == null)
{
îmage = new BitmapImage();
îmage.BeginInit();
îmage.CacheOption = BitmapCacheOption.OnLoad;
îmage.UriSource = new Uri(Path);
îmage.EndInit();
îmage.Freeze();
}
return îmage;
}
}
}
It looks as though you've been mislead by the constructor of BitmapImage that can take a Url.
If this operation really is slow enough to justify using the async-await pattern, then you would be much better off dividing it into two sections.
a) Fetching the data from the url. This is the slow part - it's IO bound, and would benefit most from async-await.
public static class MyIOAsync
{
public static async Task<byte[]> GetBytesFromUrlAsync(string url)
{
using (var httpClient = new HttpClient())
{
return await httpClient
.GetByteArrayAsync(url)
.ConfigureAwait(false);
}
}
}
b) Creating the bitmap object. This needs to happen on the main UI thread, and as it's relatively quick anyway, there's no gain in using async-await for this part.
Assuming that you're following the MVVM pattern, you shouldn't have any visual elements in the ViewModel layer - instead use a ImageItemVm for each thumbnail required
public class ImageItemVm : ViewModelBase
{
public ThumbnailItemVm(string url)
{
Url = url;
}
public string Url { get; }
private bool _fetchingBytes;
private byte[] _imageBytes;
public byte[] ImageBytes
{
get
{
if (_imageBytes != null || _fetchingBytes)
return _imageBytes;
// refresh ImageBytes once the data fetching task has completed OK
Action<Task<byte[]>> continuation = async task =>
{
_imageBytes = await task;
RaisePropertyChanged(nameof(ImageBytes));
};
// no need for await here as the continuations will handle everything
MyIOAsync.GetBytesFromUrlAsync(Url)
.ContinueWith(continuation,
TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith(_ => _fetchingBytes = false)
.ConfigureAwait(false);
return null;
}
}
}
You can then bind the source property of an Image control to the ImageBytes property of the corresponding ImageItemVm - WPF will automatically handle the conversion from byte array to a bitmap image.
Edit
I misread the original question, but the principle still applies. My code would probably still work if you made a url starting file:// but I doubt it would be the most efficient.
To use a local image file, replace the call to GetBytesFromUrlAsync() with this
public static async Task<byte[]> ReadBytesFromFileAsync(string fileName)
{
using (var file = new FileStream(fileName,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
4096,
useAsync: true))
{
var bytes = new byte[file.Length];
await file.ReadAsync(bytes, 0, (int)file.Length)
.ConfigureAwait(false);
return bytes;
}
}
Rather than involving the the dispatcher and jumping back and forth, I'd do something like this:
private Task<BitmapImage[]> GetImagesAsync(string path, int pageCount)
{
return Task.Run(() =>
{
var images = new BitmapImage[pageCount];
for (int i = 0; i < pageCount; i++)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
bitmap.Freeze();
images[i] = bitmap;
}
return images;
}
}
Then, on the UI thread calling code:
var images = await GetImagesAsync(path, pageCount);
for (int i = 0; i < pageCount; i++)
{
this._viewModel.Thumbs[i].img.Source = images[i];
}
I have WPF Application which use printing.
I have class inherite from "DocumentPaginator"
class ReportPaginator : DocumentPaginator
{
private Size pageSize;
public override IDocumentPaginatorSource Source
{
get { return null; }
}
public override bool IsPageCountValid
{
get { return true; }
}
public override int PageCount
{
get { return pageCount; }
}
public override Size PageSize
{
get { return pageSize; }
set
{
if (value != null)
{
pageSize = value;
CalculatesPage();
}
}
}
public override DocumentPage GetPage(int pageNumber)
{
// some code.
}
}
When I get FixedDocumentSequence from this paginator to preview the document before printing.
the "pageSize" property did NOT applied for this DocumentPage, and there is white spaces around the pages
How can I solve this problem
Just for the case if some one had the same problem of this.
My problem was in the my own code [specifically the part that generate the Page in the GetPage() method], I was generating white spaces around the page itself when implement GetPage method.
if you are having same problem, may be double-checking GetPage method may be helpful.
I have a DevExpress GridLookupEdit.
I am able to change the popup's default size to whatever I want via:
theGrid.Properties.PopupFormSize = New Size(mywidth, myHeight)
However, I want to save the height/width for each user.
So I run the winform's program, click it, resize the window and then close the popup'd up control.
Then the CloseUp event fires. I check theGrid.Properties.PopupFormSize and the height and width are the same as my default values.
How do I get the resized values?
I am using DevExpress 13.2
GridLookupEdit uses PopupGridLookUpEditForm object to show popup contents and store it in PopupForm property. But size of this form is not equal to size that you can set through GridLookupEdit.Properties.PopupFormSize property. This form has an EmbeddedControl property and when you are changing GridLookupEdit.Properties.PopupFormSize property, you actually changing the size of this embedded control. So, if you want to save size for each user, you need to save size of this control.
Unfortunately GridLookupEdit.PopupForm property and PopupGridLookUpEditForm.EmbeddedControl property are protected. PopupGridLookUpEditForm.EmbeddedControl actually is GridControl object. For DevExpress 14.1 you can get this object through GridLookupEdit.Properties.View.GridControl property.
So in DevExpress 14.1 GridLookupEdit.Properties.View.GridControl.Size property is what you are looking for.
But if you cannot get GridControlobject in your version then you can use reflection or create descendants.
Here example for reflection:
var popupFormProperty = theGrid.GetType().GetProperty("PopupForm", BindingFlags.NonPublic | BindingFlags.Instance, null, typeof(PopupGridLookUpEditForm), new Type[0], null);
var form = popupFormProperty.GetValue(theGrid);
var embeddedControlProperty = form.GetType().GetProperty("EmbeddedControl", BindingFlags.NonPublic | BindingFlags.Instance);
var embeddedControl = (Control)embeddedControlProperty.GetValue(form);//the size of this control is what you are looking for<
Another way is to create custom GridLookUp editor. According to documentation you need to create Custom Editor Class and Custom Repository Item Class, for example:
[UserRepositoryItem("RegisterCustomGridLookUpEdit")]
public class RepositoryItemCustomGridLookUpEdit : RepositoryItemGridLookUpEdit
{
static RepositoryItemCustomGridLookUpEdit() { RegisterCustomGridLookUpEdit(); }
static public void RegisterCustomGridLookUpEdit()
{
EditorRegistrationInfo.Default.Editors.Add(
new EditorClassInfo(CustomGridLookUpEditName,
typeof(CustomGridLookUpEdit), typeof(RepositoryItemCustomGridLookUpEdit),
typeof(GridLookUpEditBaseViewInfo), new ButtonEditPainter(), true, null));
}
public const string CustomGridLookUpEditName = "CustomGridLookUpEdit";
public override string EditorTypeName { get { return CustomGridLookUpEditName; } }
}
public class CustomGridLookUpEdit : GridLookUpEdit
{
static CustomGridLookUpEdit() { RepositoryItemCustomGridLookUpEdit.RegisterCustomGridLookUpEdit(); }
public override string EditorTypeName { get { return RepositoryItemCustomGridLookUpEdit.CustomGridLookUpEditName; } }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public new RepositoryItemCustomGridLookUpEdit Properties
{
get { return base.Properties as RepositoryItemCustomGridLookUpEdit; }
}
protected override PopupBaseForm CreatePopupForm() { return new CustomPopupGridLookUpEditForm(this); }
protected new CustomPopupGridLookUpEditForm PopupForm { get { return (CustomPopupGridLookUpEditForm)base.PopupForm; } }
public Size PopupFormSize { get { return PopupForm.PopupFormSize; } }
}
public class CustomPopupGridLookUpEditForm : PopupGridLookUpEditForm
{
public CustomPopupGridLookUpEditForm(CustomGridLookUpEdit ownerEdit) : base(ownerEdit) { }
public Size PopupFormSize { get { return EmbeddedControl.Size; } }
}
If you add this CustomGridLookUpEdit to you project then you can use its PopupFormSize property to get the required size.
I need to find UIElements in (rectangle/area/bounds).
MainWindow I'm doing the following:
I register the mouse down as the start position.
I regsiter the mouse up position.
Now I need to find ll (buttons, textboxes, etc) in the rectangle between start
postion and the end position.
I found in the msdn the HitTest approach but it is only for one point. I think, walking through all points in the founded
rectangle it is a performance disaster.
http://msdn.microsoft.com/en-us/library/ms752097.aspx
My code based on MVVM pattern:
private ObservableCollection<UIElementViewModel> wells;
private Point stratPoint; // Mouse down
public ICommand MouseUpRightCommand
{
get
{
if (this.mouseUpRightCommand == null)
{
this.mouseUpRightCommand = new RelayCommands(
param =>
{
if (param is MouseButtonEventArgs)
{
var e = (param as MouseButtonEventArgs);
//Set the end point
endPosition = e.GetPosition(((ItemsControl)e.Source));
// for example, here I want to find all controls(UIElements) in the
// founded rectangle of stratPoint and endPosition.
}
});
}
return this.mouseUpRightCommand;
}
}
Any other idea or a better approach?
Thanks
I would use FrameworkElement (which extends UIElement) instead of UIElement, in order to use ActualWidth and ActualHeight properties
Then create a static class which does some math MouseUtils
with those static fields
private static double _dContainerTop;
private static double _dContainerBottom;
private static double _dContainerLeft;
private static double _dContainerRight;
private static double _dCursorTop;
private static double _dCursorLeft;
private static double _dCursorRight;
private static double _dCursorBottom;
and those static methods
private static void FindValues(FrameworkElement element, Visual rootVisual)
{
var containerTopLeft = container.TransformToAncestor(rootVisual).Transform(new Point(0, 0));
_dContainerTop = containerTopLeft.Y;
_dContainerBottom = _dContainerTop + container.ActualHeight;
_dContainerLeft = containerTopLeft.X;
_dContainerRight = _dContainerLeft + container.ActualWidth;
}
and
public static bool IsElementUnderRectCursor(FrameworkElement element, Point startPoint, Point endPoint, Visual rootVisual)
{
_dCursorTop=Math.Min(startPoint.Y, endPoint.Y);
_dCursorBottom=Math.Max(startPoint.Y, endPoint.Y);
_dCursorLeft=Math.Min(startPoint.X, endPoint.X);
_dCursorRight=Math.Max(startPoint.X, endPoint.X);
FindValues(container, rootVisual);
if (_dContainerTop < _dCursorTop|| _dCursorBottom< _dContainerBottom )
{
return false;
}
if (_dContainerLeft < _dCursorLeft|| _dContainerRight < _dCursorRight)
{
return false;
}
return true;
}
Rootvisual being your window for example;
Then loop over ObservableCollection<FrameworkElement> wells and call that function IsElementUnderRectCursor.
This is inspired from:
Kinecting the Dots
Astreal thanks again for your answer. It's done. I just moved the selection code from modelView to view. The selection done only in the UI.
private void SelectWells(RectangleGeometry selectionRectangle, FrameworkElement frameworkElement)
{
var items = GetItemsControl(frameworkElement);
foreach (var item in items.Items)
{
var viusalItem = (ContentPresenter)items.ItemContainerGenerator.ContainerFromItem(item);
var wellControl = this.GetWellControl(viusalItem);
var relativePoint = wellControl.TransformToAncestor(items).Transform(new Point(0, 0));
var controlRectangle =
new RectangleGeometry(
new Rect(relativePoint.X, relativePoint.Y, wellControl.ActualWidth, wellControl.ActualHeight));
var intersectionGeometry = Geometry.Combine(
selectionRectangle, controlRectangle, GeometryCombineMode.Intersect, null);
if (intersectionGeometry.GetArea() > 0)
{
wellControl.Command.Execute(this);
}
}
}
usefull link for u:
http://www.codeproject.com/Articles/354853/WPF-Organization-Chart-Hierarchy-MVVM-Application
When an user clicks on a node in the tree we need to let the ViewModel node know that the selection has changed. We like to route the event as a command to the ViewModel
My question is about using bing maps with windows phone 7. Here is a summary of what I need to do
poll a service every x seconds to retrieve a set of coordinates
if this is the first time the service is polled
plot these coordinates as custom pushpins on the map (I am using Image and MapLayer)
PinObject pin = new PinObject() //Custom object
{
Id = groupMember.Id,
PushPin = new Image()
{
Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("blackpin.png", UriKind.Relative)),
Opacity = 0.8,
Stretch = System.Windows.Media.Stretch.None
},
PinLocation = new GeoCoordinate(groupMember.Latitude, groupMember.Longitude)
};
imageLayer.AddChild(pin.PushPin, pin.PinLocation); //Initialized in constructor
pinObjects.Add(pin);// Add pin object to a list to provide a handle to the objects
auto set the map zoomlevel so that all the plotted points are visible (I would assume using a LocationRect.CreateLocationRect should do)
var coords = pinObjects.Select(p => p.PinLocation).ToList();
myMap.SetView(LocationRect.CreateLocationRect(coords));
else based on the new coordinates obtained, update the locations of each of the pushpins on the map
PinObject pObj = pinObjects.FirstOrDefault(p => p.Id == groupMember.Id);
MapLayer.SetPosition(pObj.PushPin, new GeoCoordinate(groupMember.Latitude, groupMember.Longitude));
The pins load fiine and the call to the service to get their new locations loads fine as well, the problem is that their location on the map is never updated so basically they just sit still even though all this work is going on in the background, I have debugged so I know it works. How do I reset the location of the pins, if using an Image won't do, can I use a Pushpin object? How would this work?
Thanks in advance.
I've found that the best way to ensure the pushpins get updated is to call call SetView() on the map again. You can pass in the existing view to basically force a refresh. Eg; MyMapControl.SetView(MyMapControl.BoundingRectangle);
Here is an option similar to Dispatcher.BeginInvoke but it works better for me in some cases. When I really need to get off the current thread with some work I will use a private static class UICallbackTimer to offset execution just a slight amount. (typos and untested code, just pulling out pieces here you'll have to debug in your code)
UICallbackTimer is not my code but it's available on the Internet. You can get information on this class by searching "private static class UICallbackTimer"
Here is the code to execute it.
UICallbackTimer.DelayExecution(TimeSpan.FromSeconds(.01),
() => (
MapLayer.SetPosition(pObj.PushPin, new GeoCoordinate(groupMember.Latitude, groupMember.Longitude))
);
and here is the class ( I place it inside the current object, so that it remains private to my class) Add using statement for System.Threading
private static class UICallbackTimer
{
private static bool _running = false;
private static int runawayCounter = 0;
public static bool running()
{
if (_running && runawayCounter++ < 10)
return _running;
runawayCounter = 0;
_running = false;
return _running;
}
public static void DelayExecution(TimeSpan delay, Action action)
{
_running = true;
System.Threading.Timer timer = null;
SynchronizationContext context = SynchronizationContext.Current;
timer = new System.Threading.Timer(
(ignore) =>
{
timer.Dispose();
_running = false;
context.Post(ignore2 => action(), null);
}, null, delay, TimeSpan.FromMilliseconds(-1));
}
}
Great Question!
Here is some realllly ugly code, but at least its working and something to start from.
I got the main structure from here. I'd appreciate it if someone could post an answer with proper binding and less code behind.
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--RESOURCES-->
<Grid.Resources>
<DataTemplate x:Key="LogoTemplate">
<maps:Pushpin Location="{Binding PinLocation}" />
</DataTemplate>
<maps:MapItemsControl x:Name="GroupAPins"
ItemTemplate="{StaticResource LogoTemplate}"
ItemsSource="{Binding PinsA}">
</maps:MapItemsControl>
</Grid.Resources>
<Grid x:Name="ContentPanel" Grid.Row="0" Margin="12,0,12,0"/>
</Grid>
public partial class MapPage : PhoneApplicationPage
{
private ObservableCollection<PinData> _pinsA = new ObservableCollection<PinData>();
private Map MyMap;
public ObservableCollection<PinData> PinsA { get { return this._pinsA; } }
public MapPage()
{
InitializeComponent();
this.DataContext = this;
//Create a map.
MyMap = new Map();
MyMap.CredentialsProvider = new ApplicationIdCredentialsProvider("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
//Remove the List of items from the resource and add it to the map
this.LayoutRoot.Resources.Remove("GroupAPins");
MyMap.Children.Add(GroupAPins);
MyMap.Center = new GeoCoordinate(40.74569634433956, -73.96717071533204);
MyMap.ZoomLevel = 5;
//Add the map to the content panel.
ContentPanel.Children.Add(MyMap);
loadAPins_fromString();
}
//ADD PIN TO COLLECTION
private void addPin(String lat, String lon)
{
PinData tmpPin;
tmpPin = new PinData()
{
PinLocation = new GeoCoordinate(System.Convert.ToDouble(lat), System.Convert.ToDouble(lon))
};
_pinsA.Add(tmpPin);
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += delegate(object sender, EventArgs args)
{
PinsA.Remove(tmpPin);
tmpPin.PinLocation.Latitude += 1;
PinsA.Add(tmpPin);
};
timer.Start();
}
//LOAD PINS ONE BY ONE
private string loadAPins_fromString()
{
//BAD
addPin("42.35960626034072", "-71.09212160110473");
//addPin("51.388066116760086", "30.098590850830067");
//addPin("48.17972265679143", "11.54910385608672");
addPin("40.28802528051879", "-76.65668606758117");
var coords = PinsA.Select(p => p.PinLocation).ToList();
MyMap.SetView(LocationRect.CreateLocationRect(coords));
return "A PINS LOADED - STRING";
}
}
public class PinData
{
public GeoCoordinate PinLocation{get;set;}
}