Professional Documents
Culture Documents
Alberto Población
Notice of Liability
The author and publisher have made every effort to ensure the accuracy of the
information herein. However, the information contained in this book is sold without
warranty, either express or implied. Neither the authors and Krasis Consulting S.L.,
nor its dealers or distributors, will be held liable for any damages to be caused either
directly or indirectly by the instructions contained in this book, or by the software or
hardware products described herein.
Trademark Notice
Rather than indicating every occurrence of a trademarked name as such, this
book uses the names only in an editorial fashion and to the benefit of the trademark
owner with no intention of infringement of the trademark.
ISBN: 978-84-935489-3-3
PRINTED IN THE UNITED STATES O F AMERICA
Acknowledgments
First, I would like to give my thanks to Jose Manuel Alarcón, who suggested that I should
write this book, followed its progress, and encouraged me to go on until it was complete.
Before I started writing this book, I had in my hands a lot of short texts and coding examples
that I had created while answering questions in public forums or presenting examples in
training courses. I give my thanks to the people who asked the questions that prompted me to
create these examples and those who participated in the discussions that allowed me to refine
them.
Some of those pieces have served me as inspiration more than once, since I based on them a
few examples presented in this book and they also supported a few short articles that I
published in the magazine “dotNetMania.” I would like to give a special thank-you to its
editor, Paco Marín, who gave me permission to reuse here some of those ideas.
And last, but not least, I thank my wife, who encouraged me while I was writing and helped
me find the time to write when other obligations seemed to always keep me away from this
task.
Contents
FOREWORD ........................................................................................................... ix
PREFACE ................................................................................................................ xi
The Microsoft .NET Framework contains thousands of libraries to help with just
about any modern development task. You can use the .NET Framework to access data,
create a user interface, run algorithms or consume a service. The challenge as a .NET
developer becomes to learn what types of libraries are available, how to use them and
when. This book helps guide Windows® developers through common questions for a
variety of common .NET development scenarios.
Why focus on Windows development? With more than a billion clients in the world
today, Windows is by far the largest available platform. Windows 7, in particular, has
been the fastest growing and most popular release of Windows yet. See the Windows 7
chapter in this book to learn how your applications can light up on this huge, growing
platform. A number of the tips in this book are also relevant to other .NET application
types.
As a Visual Studio community program manager, I have the opportunity to interact
with many customers, including the Microsoft Most Valuable Professional (MVP)
program. The MVP Program honors Microsoft technology experts for their impact in
the community. Alberto Población has been awarded as a C# MVP because of his great
impact teaching others about .NET development, both as a trainer as well as a top
answerer in the newsgroups and forums.
Tips and tricks content is always incredibly popular because of its immediate
practical use. It is also very personal, based on the author‟s experience and
prioritization. Alberto‟s selections in this book draw on nearly 30 years of development
and reflect the top questions raised in his interactions with .NET users. Reading this
book will help you expand your toolset, complete tasks more quickly, and write better
and more resilient code. Alberto‟s writing style is easy to follow and engaging to read.
You can keep it on your shelf as a reference or read it cover to cover as a novel.
So, pack this guide with you, give it a read and add some new tricks to your
toolbox!
Lisa Feigenbaum
Community Program Manager
Microsoft Visual Studio
ix
Preface
xi
xii .NET Windows Development Everyday Tips, Tricks & Optimization
you use to access them. Therefore, they should be useful for developers writing code in
VB.NET or any other .Net language.
If you wish to run the examples without having to type them from the book, you can
download the completed sample projects from the website of the publisher.
Most of the projects were done with Visual Studio 2008, but will migrate without
effort to Visual Studio 2010. A small number of the samples, mainly those that present
some of the latest features in Windows or in Visual Studio, were done with Visual
Studio 2010, and are not directly compatible with earlier versions.
And now, with no further ado, let‟s go ahead into the first chapter.
6
CHAPTER
Many developers whose native language is English never deal with text that
contains characters other than those in the ASCII table. This encoding uses 7 bits to
codify a set of symbols, numbers and all the letters that are used in English. However,
other languages use additional characters such as letters with diacritical marks (áèü) or
additional symbols (åøñ). And we are not even speaking about non-Latin alphabets
such as Greek, Russian or Chinese.
A typical way to read a text file uses a StreamReader, in a way that is similar to
what we see in the following code fragment:
using System.IO;
...
using (StreamReader sr = new StreamReader(filename))
{
string s = sr.ReadLine();
textBox1.Text = s;
}
189
190 .NET Windows Development - Everyday Tips, Tricks & Optimization
This will work well with a file that only contains ASCII characters. But let‟s do an
experiment. Open Notepad, type-in a sentence that contains some foreign characters,
and save it into a file on disk. For instance:
Note: if you are using a U.S. keyboard, it does not directly generate any of these
special characters. You can insert them from the “Character Map” utility that is usually
found under Start -> All Programs -> Accessories -> System Tools. You can also press
the Alt key, enter the numeric code for the character on the numeric keypad, and
release the Alt key. For instance, the “é” character that we used can be entered as
Alt+130.
Now, run the piece of code above, so that it reads the file that was saved in
Notepad. You will note that the non-ASCII characters come out wrong:
As you can see, the textbox shows small rectangles in place of the “special”
characters.
Notepad saved the file using an encoding named “ANSI” (the “Save As...” dialog
offers other choices, but ANSI is selected by default). This encoding uses 8 bits to
encode each character. The first 127 characters match the ASCII table, so there is no
problem when reading pure English text. But the “top” 128 characters represent various
symbols such as the “é” and “¿” that we typed in our sample. The specific mapping of
characters depends on the regional version of Windows. The example above was run
Various techniques that should be well-known, but sometimes aren’t 191
on an operating system that was using the “Western European” character set, also
known as “Windows-1252”.
If you wish to see the actual codes that were written into the file, you can open it
with a binary editor. You can do this from Visual Studio if you add the file into the
project. Then, right-click on it in Solution Explorer and select Open With -> Binary
Editor.
Here you can see, for instance, that the “é” was encoded as 0xE9.
All Strings in .Net are Unicode, and they are stored in memory using the UTF-16
encoding, which encodes each character in 16 bits (well, this is only true for characters
in the Basic Multilingual Plane, but in practice it is unlikely that you will be using any
other of the Unicode planes). This is enough for encoding most of the characters in all
languages, so you do not have to worry about encodings when manipulating character
strings inside your programs.
At some point, when you are reading the 8-bit data from the file into the Unicode
String in memory, there has to be a conversion that “translates” the characters from one
encoding into the other. When reading the file with a piece of code like the one in our
first example, the StreamReader itself performs the conversion. By default, it tries to
interpret the file as UTF-8. This is an encoding that stores Unicode characters using 8-
bit data. The first 127 characters are stored in a single byte, and they match the ASCII
table, so this process is not apparent when reading English text. However, “special”
characters are stored using more than one byte. The first byte serves as a sort of
“prefix” to indicate that something special comes next. This is what our file would
contain if saved as UTF-8:
As you can see, the “é” is now encoded as 0xC3 0xA9, which is quite different from
its ANSI encoding.
When the StreamReader attempts to interpret the file according to the UTF-8
encoding, but instead finds characters encoded as ANSI, it fails to interpret them
correctly. Sometimes, a character is misinterpreted so that a different character is
shown instead. In other cases, the specific combination of bits may not even
correspond to any character in the target encoding, so a small rectangle is displayed (as
we saw earlier) to indicate the error.
192 .NET Windows Development - Everyday Tips, Tricks & Optimization
In order to interpret correctly the contents of the file, we need to tell the
StreamReader which is the encoding for the file that it is reading. This can be done by
means of a parameter of type System.Text.Encoding that can be passed to the
constructor:
using System.Text;
...
Encoding encoding = Encoding.GetEncoding(1252);
using (StreamReader sr = new StreamReader(filename, encoding))
{
string s = sr.ReadLine();
textBox1.Text = s;
}
The preceding code would display properly the content of the file, if it was written
using the Western European (“Windows-1252”) character set.
The Encoding class has static methods that provide the most usual encodings, such
as Encoding.UTF8 or Encoding.ASCII. It also has a GetEncoding method that can
obtain an encoding from its number (as shown above) or from its name, such as
Encoding.GetEncoding("Windows-1252").
There is no reliable way to determine the encoding that was used to create a file.
You need to be familiar with the application that created the file, and the platform on
which it was run, so that you can deduce the Encoding that you need to specify when
opening the file. If you use the wrong encoding, the characters will come out all wrong.
For instance, if you thought that our example ANSI file was created on a Greek version
of Windows, you might try to open it using encoding “Windows-1253” (which is the
Greek encoding). The code would be exactly the same except that you would change
the number for the encoding:
The same binary codes in the file on disk have now been interpreted as Greek
characters, since we told the StreamReader to use that encoding; this is why we are
seeing an omega and an iota instead of the original characters that we wrote into the
file. Of course, an analogous failure when reading the text would occur if the file had
actually been written in Greek (1253) and we tried to interpret it as Western European
(1252). So it is important to specify the correct encoding when accessing the file.
Up to this point, we have talked about using .Net code for reading a file that was
created with an external tool, but the same applies when writing a file that needs to be
in a specific format so that it can be read properly by a different application. Normally,
you would write a text file with a StreamWriter:
Notice how the file prefix is displayed as a sequence of strange characters and the
pair of bytes that encodes each of our special characters is interpreted as a pair of
different characters.
If we want the file to display correctly, we need to save it using the same encoding
that is expected by the program that is going to interpret it later. In this case, this could
be, for instance, encoding 850 (“Western European DOS”) or 437 (“OEM United
States”), depending of the version of Windows. The encoding is passed in the
constructor of the StreamWriter, similarly to what we did for the StreamReader:
194 .NET Windows Development - Everyday Tips, Tricks & Optimization
If we write the file using this code, it will display correctly in the Command Prompt
window, as shown in the following figure:
Of course, now it will fail to display correctly if it is opened with Notepad, since
this program expects a different encoding. Once more, we are reminded that it is
important to know which program is going to use our file, so that it can be saved with
the expected encoding.
So, which encoding should you use if you are writing a program that needs to save
text files and is later going to read them?
If you are in control of both the reading and writing processes, you can use any
encoding as long as it is the same one for reading and for writing. If you wish to
support the whole range of Unicode characters without loss of information, you should
use one of the encodings that support Unicode. If your text contains mostly ASCII
characters, UTF-8 is probably the best choice, since it will encode the majority of
characters using only one byte. It also happens to be the default if you do not specify
any encoding for the StreamReader and StreamWriter.
If the program needs to be compatible with legacy Windows applications, you
probably want to use one of the ANSI encodings. As we saw earlier, there are several
variations depending on the locale of the operating system. In most cases, you will
want your program to use the default encoding, so that the files are compatible with
other locally installed applications. You can get the operating system's current ANSI
code page by means of System.Text.Encoding.Default.
To summarize: the next time that you write an application that might need to handle
international text, keep in mind that not necessarily every character maps to one byte,
and even if you can fit the whole character set that the application uses into 8 bits, there
are different ways to perform this mapping. Therefore, you will need to devote a little
bit of time to consider the Encoding that you will use, and apply it at the appropriate
places.
This is quite simple, but you would be surprised at the number of times that this
question appears in programming forums, even from people who are reasonably
experienced at developing windows forms applications.
Typically, the author of the question is writing an application that has a main form,
possibly an MDI container, which has a button or menu option for opening a second
form (usually, but not necessarily, an MDI child). Of course, opening the secondary
form is quite easy:
By the way, this is (almost) the default code provided by Visual Studio when you
add a new MDI form to a Windows Forms project.
However, if you press repeatedly the button to open a new form, several copies of
the form will be opened, because each time a new instance of Form1 is created and
shown.
A first approach to avoid this situation is to keep a reference to the child form in a
class variable and verify whether it is initialized before opening a second copy of the
form:
If the reference is null, we create a new form and show it. If it is not null, we take
whatever actions are needed to make it visible (for instance, it could be minimized or
hidden beneath other forms). This works, but it has a drawback: What if the user closes
the form? The reference will still be not null, so the program will not open the form
again.
There are several possible solutions to this situation. The first is to check not only
whether the reference to the child form is null, but also if it has been disposed:
This works, in general, but is not completely reliable because it depends on the form
being disposed when it is closed. However, there are some circumstances when this
does not happen. For instance, the documentation asserts that if an MDI form is hidden
and then its Close method is called, the form is not disposed.
Another alternative is to connect a handler to the FormClosed event, and use this to
clear the reference to the form:
{
if (childForm == null)
{
childForm = new Form1();
childForm.MdiParent = this;
childForm.FormClosed +=
new FormClosedEventHandler(childForm_FormClosed);
childForm.Show();
}
else
{
childForm.Show();
if (childForm.WindowState == FormWindowState.Minimized)
childForm.WindowState = FormWindowState.Normal;
childForm.BringToFront();
}
}
void childForm_FormClosed(object sender, FormClosedEventArgs e)
{
childForm = null;
}
Yet another option is to loop over all the child forms trying to find the one that we
wish to show. If found, we make it visible; otherwise, a new form is created:
This is perhaps easier to understand, since it does not require connecting an event,
but it may be a little less efficient due to the need for looping over all the forms. Note
that this last method only works for MDI forms, while the previous ones would be
valid for any kind of form. If you want to use this method with any arbitrary forms, you
can substitute Application.OpenForms for this.MdiChildren.
It wasn‟t that difficult, was it? So, why are there so many developers who ask about
this subject? Well, a possible reason is that many of them are programmers who
previously developed applications with VB6 (or earlier), and have migrated to .Net.
Old VB6 had a feature that is no longer present in .Net: For every form in the project,
the compiler automatically issued a statement like this one:
This led many developers to not make the distinction between the class and the
instance, because they always had a reference to an instance with the same name as the
class. Also, declaring something “...As New type” had an implication that no longer
holds true in .Net, namely, “every time the object is accessed, verify that it is not
Nothing, and if it is, automatically create a new instance”. Therefore, it was easy to
simply do “Form1.Show” at any time, and it always showed the same form. This will
not work in .Net, and it causes much confusion for VB6 programmers, including their
failure to understand why Form1.TextBox1 doesn‟t work. So the important lesson for
developers who have migrated in this way is that they need to understand the difference
between a class and an instance of that class, and that the name of a form cannot be
used implicitly to reference an instance of that form.