WPF: Why TOO SLOW to Get PrintDialog's .PrintableAreaWidth and .PrintableAreaHeight? - wpf

I am experiencing an extremely wired WPF PrintDialog issue -- Windows XP64 + VS2010.
It is pretty unbelievable that it is very very slow to get PrintDialog's .PrintableAreaWidth or .PrintableAreaHeight property.
// see sample codes below - remember to include "using System.Windows.Controls"
PrintDialog pd = new PrintDialog();
double pw = pd.PrintableAreaWidth; // set a break-point here, very slow, why???
double ph = pd.PrintableAreaHeight;
Anyone has any idea regarding this? I appreciate any thoughts!

The PrintableArea refers to the actual Printer in use - your app has to contact the printer to get that info, and my guess is that's the reason why it's slow. It shouldn't be any faster in WinForms...
If you'd want to optimize, you could cache the printer name and it's defaults and use that instead of querying the printer each time.

Related

Cannot Browse to specific Type in Settings designer for WPF/.net Core application

When I've used Settings Designer before, I've been able to browse to find non-standard Types (e.g. uncommon enums etc) to use in my Settings via a "Browse" button at the bottom of the drop down under the "Type" column. I'm developing a WPF desktop application for .net Core and there is no Browse option as pictured below:
I did go into the code behind (Settings.Designer.cs.) and edit the code manually, but on saving, this just reverted to string. I'm guessing this may have something to do with settings also having an element in App.config and I notice it has a "serialiseAs" tag - didn't know what to put here. Exmaple of the code behind settings and App.config:
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string UiTheme {
get {
return ((string)(this["UiTheme"]));
}
set {
this["UiTheme"] = value;
}
}
<userSettings>
<GameBoxer.WPF.Properties.Settings>
<setting name="UiTheme" serializeAs="String">
<value />
</setting>
</GameBoxer.WPF.Properties.Settings>
</userSettings>
Does anyone know how to bring back the 'Browse'?? Or, how to correctly do it in code?
I'm using Visual Studio 2022 Community
Thanks
UPDATE: So, I learn that this is "By Design" in VS2022 according to MS here. It's still present in VS2019! But they've taken it out of VS2022 and I can't figure how to do it in code. MS, you're one of my faves out the bunch, but sometimes, you're as mad as a box of frogs. unfortunately that link doesn't provide the poster with any alternatives other than "that's not a bug." Not very helpful, really.
As mentioned in the link you provided, this change was by design due to .NET Core and while I very strongly disagree with their stance on this - I'm assuming this was done because it could be quite fiddly to get your own types to work as expected, especially for new users.
One simple workaround if your custom data has several values, you can use string and simply write your own little parser using delimiters such as ;. You could also use StringCollection to achieve the same result.
Inconvenient, yes. But a simple solution nonetheless.
I sincerely hope Microsoft changes their stance on this and looks at reimplementing this as it worked remarkably well once you figured out the procedure to get it to serialize properly.
Edit:
Figured I might as well provide an example;
// Storing the Settings
// Parameter: Struct { Location(Point), Size(Point), Margin(Thickness) }
var settingString = $"{e.Location.X};{e.Location.Y};{e.Size.X};{e.Size.Y};{e.Margin.Left};{e.Margin.Top};{e.Margin.Right};{e.Margin.Bottom}";
Properties.Settings.Default.MySetting = settingString;
Properties.Settings.Default.Save();
// Parsing the Saved Setting
var settingString = Properties.Settings.Default.MySetting;
if (!String.IsNullOrWhiteSpace(settingString))
{
List<string> splitStrings = settingString.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList();
List<double> parsedValues = new List<double>();
splitStrings.ForEach(x => parsedValues.Add(double.Parse(x)));
var location = new Point(parsedValues[0], parsedValues[1]);
var size = new Point(parsedValues[2], parsedValues[3]);
var margin = new Thickness(parsedValues[4], parsedValues[5], parsedValues[6], parsedValues[7]);
}
There's probably better ways of doing this, but I find this to be a very simple workaround and has worked great thus far.

Identical fonts don't look identical on high DPI monitors

I've got a WinForms application that works great on older systems, but I'm having trouble making it look good on 4k monitors. There are multiple issues, and a lot written on the subject, but this question is focused on one specific problem. I can set different controls to use the same font, but on high DPI systems, the controls will look a lot different. How can I fix this?
Obviously I can change the font size, move controls around, etc. But Windows is adding a mysterious factor into my font sizes. Without knowing what Windows is doing, it's hard for me to undo it!
On an older system my test window looks perfect:
On a high DPI system, some controls have a different font size than others:
I've tried several things, including manually setting the font on some controls rather than inheriting from the form. As you can see, changing the font did not fix the problem:
After searching the Internet I've tried several things to fix this including:
Changing the application between PROCESS_DPI_UNAWARE, PROCESS_SYSTEM_DPI_AWARE, and PROCESS_PER_MONITOR_DPI_AWARE
Explicitly changing the font rather than using the form's font.
Building on an old system vs building on a high DPI system.
Building on a monitor set to 96 DPI / 100% vs building on a monitor set to 192 DPI / 200% on the same computer.
Building the form in visual studio's designer vs building it in pure C# code.
.Net 4.0 vs. .Net 4.6.1
Visual Studio 2010 vs Visual Studio 2015
I only found one thing that fixed my problem. Unfortunately I had to do it on the target machine, not on the machine where I'm building this. So it's not a practical solution. See the second item under "steps to repeat" for more details.
Steps to repeat:
This happens with a lot of controls on a lot of forms. See the code sample below for a small, simple demo. That's how I got the screenshots, above.
I can make this problem appear or disappear with one system setting. If you change the main monitor to 96 DPI / 100% scaling, then reboot, you'll get the good result where all fonts are as requested. If you change the main monitor to a different DPI setting, then reboot, you'll see the bad results.
private void newFormButton_Click(object sender, EventArgs e)
{
Font copyOfFont = new Font(Font, FontStyle.Strikeout);
Form form = new Form();
form.Font = Font;
string sample = "Abc 123 :)";
int padding = 6;
Label label = new Label();
label.Text = sample;
label.Top = padding;
label.Left = padding;
label.Font = copyOfFont;
label.Parent = form;
Button button = new Button();
button.Text = sample;
button.Top = label.Bottom + padding;
button.Left = padding;
button.Width = label.Width + padding * 2;
button.Height = label.Height + padding * 2;
button.Parent = form;
TextBox textBox = new TextBox();
textBox.Text = sample;
textBox.Size = button.Size;
textBox.Top = button.Bottom + padding;
textBox.Left = padding;
textBox.Parent = form;
ListBox listBox = new ListBox();
listBox.Items.Add(sample);
listBox.Items.Add(sample);
listBox.Width = button.Width;
listBox.Height = button.Height * 2;
listBox.Top = textBox.Bottom + padding;
listBox.Left = padding;
listBox.Font = copyOfFont;
listBox.Parent = form;
form.Show();
}
This is crazy but it works.
Everything I've seen on the internet about DPI Virtualization says that Windows will automatically set a process to PROCESS_DPI_UNAWARE by default. So unless you explicitly pick one of the other two settings, your application should look decent on a high resolution monitor. It might be a little fuzzy, but it shouldn't look as bad as the examples I've shown above.
Apparently that's not true. The default depends on the computer, and it depends on the day. My solution: Explicitly set the application to use PROCESS_DPI_UNAWARE. I've included a code sample below.
Note that you should be able to take care of this using the manifest. Some sources say that's the preferred way, rather than using C# code. We've had mixed results with that. The C# code option seems more reliable.
[DllImport("shcore.dll")]
static extern int SetProcessDpiAwareness(_Process_DPI_Awareness value);
enum _Process_DPI_Awareness
{
Process_DPI_Unaware = 0,
Process_System_DPI_Aware = 1,
Process_Per_Monitor_DPI_Aware = 2
}
public MainForm()
{
//int result = SetProcessDpiAwareness(_Process_DPI_Awareness.Process_System_DPI_Aware);
//int result = SetProcessDpiAwareness(_Process_DPI_Awareness.Process_Per_Monitor_DPI_Aware);
int result = SetProcessDpiAwareness(_Process_DPI_Awareness.Process_DPI_Unaware);
System.Diagnostics.Debug.Assert(result == 0);
This works on a number of different developer machines. We're about to start sending the fix out to beta testers.
Summary
The O/S provides a compatibility mode for old programs running on high DPI systems.
WinForms and the O/S provide tools for manually changing the sizes of your controls depending on the DPI of the system
or the current monitor.
Both #1 and #2 are both seriously buggy!
The details var a lot from one computer to the next.
Fixing #2 would be the more
powerful option, but as far as I can tell it would be impossible to
fix that.
Instead I fixed #1. That works reasonably well.

Setting System.Windows.Media.FontFamily("Calibri") doesn't work on other PCs

For a WPF application where I need to display numbers in scientific notation (eg., 10² 10ⁿ), I've used code like below. I'm assigning these "Run" variables to a TextBlock, I have to do this from code-behind file in a Converter and hence this C# code and I cannot use XAML.
The issue that I'm noticing is that on my development machine it works fine, displays as expected like 10², whereas in others' machines, I see this as 102, instead of superscript.
Can anyone please let me know how to troubleshoot this or what I'm missing?
Btw, the reason I'm setting the FontFamily specifically is apparently I found out that not all font families support superscripts.
Run logBase = new Run();
logBase.FontFamily = new System.Windows.Media.FontFamily("Calibri");
logBase.Text = "10";
Run logExp = new Run();
logExp.FontFamily = new System.Windows.Media.FontFamily("Calibri");
logExp.Text = "2";
logExp.Typography.Variants = FontVariants.Superscript;
What are the specs of the other machines? What's different about them? Are they on XP? Calibri was first distributed with Vista, so XP machines won't have it by default. Try a different font and see if you have the same issue.
http://en.wikipedia.org/wiki/Calibri

Silverlight replacement for BinaryReader.ReadDecimal

In Silverlight 4, BinaryReader doesn't seem to have any ReadDecimal() method.
Reflector shows that it's there but with internal visibility, rather than public.
Aside from using that one via dynamic trickery or Reflection, has anyone got a good workaround for getting it. Or is this all part of the plan?
Erica Aside: amusingly, Reflector also shows that there are 10 InternalsVisibleToAttributes in the Ag mscorlib (sadly none to mine :D), which I assume, at 512+ bytes a go gives plenty scope for optimization! I'm sure Bob is in there too :D
There is no direct replacement, but you can achieve the same result like this:
// write it, assume bw = BinaryWriter
var bits = decimal.GetBits(myDecimal);
bw.Write(bits[0]);
bw.Write(bits[1]);
bw.Write(bits[2]);
bw.Write(bits[3]);
// read it, assume br = BinaryReader
var bits = new int[4];
bits[0] = br.ReadInt32();
bits[1] = br.ReadInt32();
bits[2] = br.ReadInt32();
bits[3] = br.ReadInt32();
return new decimal(bits);

WPF Printing Flow Document

Greetings,
I have a problem with printing in WPF.
I am creating a flow document and add some controls to that flow document.
Print Preview works ok and i have no problem with printing from a print preview window.
The problem exists when I print directly to the printer without a print preview. But what is more surprisingly - when I use XPS Document Writer as a printer
everyting is ok, when i use some physical printer, some controls on my flow document are not displayed.
Thanks in advance
Important thing to note : You can use XpsDocumentWriter even when printing directly to a physical printer. Don't make the mistake I did of avoiding it just because you're not creating an .xps file!
Anyway - I had this same problem, and none of the DoEvents() hacks seemed to work. I also wasn't particularly happy about having to use them in the first place. In my situation some of the databound controls printed fine, but some others (nested UserControls) didnt. It was as if only one 'level' was being databound and the rest wouldn't bind even with a 'DoEvents()' hack.
The solution was simple though. Use XpsDocumentWriter like this. it will open a dialog where you can choose whichever installed physical printer you want.
// 8.5 x 11 paper
Size sz = new Size(96 * 8.5, 96 * 11);
// create your visual (this is a WPF UserControl)
var template = new PackingSlipTemplate()
{
DataContext = new PackingSlipViewModel(order)
};
// arrange
template.Measure(sz);
template.Arrange(new Rect(sz));
template.UpdateLayout();
// print to XpsDocumentWriter
// this will open a dialog and you can print to any installed printer
// not just a 'virtual' .xps file
PrintDocumentImageableArea area = null;
XpsDocumentWriter xps = PrintQueue.CreateXpsDocumentWriter(ref area,);
xps.Write(template);
I found the OReilly book on 'Programming WPF' quite useful with its chapter on Printing - found through Google Books.
If you don't want a print dialog to appear, but want to print directly to the default printer you can do the following. (For me the application is to print packing slips in a warehouse environment - and I don't want a dialog popping up every time).
var template = new PackingSlipTemplate()
{
DataContext = new PackingSlipViewModel(orders.Single())
};
// arrange
template.Measure(sz);
template.Arrange(new Rect(sz));
template.UpdateLayout();
LocalPrintServer localPrintServer = new LocalPrintServer();
var defaultPrintQueue = localPrintServer.DefaultPrintQueue;
XpsDocumentWriter xps = PrintQueue.CreateXpsDocumentWriter(defaultPrintQueue);
xps.Write(template, defaultPrinter.DefaultPrintTicket);
XPS Document can be printed without a problem
i have noticed one thing:
tip: the controls that are not displayed are the controls I am binding some data, so the conclusion is that the binding doesn't work. Can it be the case that binding is not executing before sending the document to the printer?

Resources