CS411 — Midterm Summary (Lectures 1–22)
📘 Lecture 1 — Visual Programming
📖 Overview: This introductory lecture sets the foundation for understanding visual programming as an event-driven paradigm applied to desktop, web, and mobile applications. It demonstrates the shift from traditional sequential programming to a model where programs react to events, using simple C++ examples to illustrate the core concepts.
🗂️ Topics Covered
This lecture introduces the course scope focusing on graphical user interfaces and event-driven programming across desktop, web, and mobile platforms. It presents two books as primary references: "Event processing in action" and "Windows presentation foundation unleashed". The lecture demonstrates the evolution from basic sequential input/output programs to event-reactive programs using keyboard input and file-change detection as event sources.
📝 Lecture Summary
Introduction to Visual Programming
This course focuses on graphical user interfaces and the event-driven model applied to desktop, web, and mobile applications. The prerequisites are C++ programming and data structures. The course takes a hands-on approach with in-class examples and programming assignments.
The primary textbooks are:
- "Event processing in action" by Opher Etzion and Peter Niblett
- "Windows presentation foundation unleashed" by Adam Nathan
The course covers event-driven programming in the browser using AJAX techniques and mobile event-driven programming, with no prerequisites for these topics.
💡 Why this matters: Visual programming is fundamentally different from traditional sequential programming - instead of the program controlling the flow, user actions and system events drive the program's behavior.
First Example - Basic Input/Output
The first example demonstrates a simple sequential program that reads keyboard input and echoes it:
#include <iostream>
using namespace std;
int main() {
char a;
do {
a = cin.get();
cout << a;
} while (a != 'x');
return 0;
}
🔑 Definition — Sequential programming: A programming model where the program executes instructions one after another in a predetermined order, with no external interruptions.
📐 Formula: cin.get() → Reads a single character from keyboard input
📌 Example: When you run ./example1 and type "hello", the program outputs "hello" immediately. Typing 'x' terminates the program.
Second Example - File Monitoring
The second example adds file monitoring capability, creating a busy wait loop:
#include <iostream>
#include <fstream>
int main() {
char a = '-';
do {
std::ifstream f("test.txt", std::ifstream::in);
if (f.good() && a != f.peek()) {
a = f.get();
std::cout << a << std::endl;
}
f.close();
} while (a != 'x');
return 0;
}
🔑 Definition — Busy wait: A programming technique where the program continuously checks for a condition in a tight loop, consuming CPU resources even when no events are occurring.
📌 Example: This program constantly opens and reads "test.txt" file checking for changes, wasting CPU cycles if nothing changes. If you manually edit the file and add 'x', the program terminates.
Third Example - Multiple Event Sources
This example combines both keyboard and file monitoring using a hypothetical iskeyboardpressed() function:
#include <iostream>
#include <fstream>
int main() {
char a = '-', b;
do {
if (iskeyboardpressed()) {
b = std::cin.get();
std::cout << b << std::endl;
}
std::ifstream f("test.txt", std::ifstream::in);
if (f.good() && a != f.peek()) {
a = f.get();
std::cout << a << std::endl;
}
f.close();
} while (a != 'x' && b != 'x');
return 0;
}
🔑 Definition — Event source: An entity that can generate events that the program can respond to, such as keyboard input or file system changes.
📌 Example: Now the program responds to TWO different events - keyboard presses AND file changes. If you type 'x' OR if the file contains 'x', the program terminates.
Refactored Version - Cleaner Event Handling
The improved version demonstrates event-driven programming principles with separated event handlers:
#include <iostream>
#include <fstream>
int onkeypress() {
char b = std::cin.get();
std::cout << b << std::endl;
return b == 'x';
}
int onfilechanged() {
char a = f.get();
std::cout << a << std::endl;
return a == 'x';
}
int main() {
char a = '-', b;
do {
if (iskeyboardpressed()) {
if (onkeypress())
return 0;
}
std::ifstream f("test.txt", std::ifstream::in);
if (f.good() && a != f.peek()) {
if (onfilechanged())
return 0;
}
f.close();
} while (true);
return 0;
}
🔑 Definition — Event-driven programming: A programming paradigm where the program's flow is determined by events - actions such as user inputs, sensor outputs, or messages from other programs.
📌 Example: Each event (keypress or file change) has its own handler function. The program is more organized, focused, and scales better to handle many different event types.
⭐ Key Takeaways
The core transformation from sequential to event-driven programming requires understanding that programs must be designed to react to multiple, potentially simultaneous events rather than following a predetermined path. The refactored approach demonstrates better code organization and scalability but introduces blocking as a significant challenge. The lecture establishes that event-driven programming, while initially more complex, scales effectively to handle large numbers of event sources in GUI applications. The key distinction is between programs that actively wait (busy wait) and those that efficiently respond to events as they occur.
🧠 Quick Revision Questions
- What is the fundamental difference between sequential programming and event-driven programming?
- Why is busy waiting inefficient in event-driven programs?
- What are the two primary textbooks used in this course and what topics do they cover?
- How does the refactored version of the program improve upon the initial multiple-event-source version?
- What platforms does this course cover for event-driven programming applications?
📘 Lecture 2 — Event Driven Design
📖 Overview: This lecture builds on the introduction to event-driven design by exploring the concept of events in-depth, including their definition, types, and processing. It explains why event-based programming is crucial for visual programming and real-world applications, using analogies and a detailed flower delivery example to illustrate event-driven architecture.
🗂️ Topics Covered
The lecture covers the definition of an event in computing, the distinction between synchronous and asynchronous behavior, the nature of event processing and event-based programming, the benefits of event-driven design, types of events in non-real-time applications, and concludes with a comprehensive flower delivery example that demonstrates how events drive a real-world system.
📝 Lecture Summary
Event Refactoring and Definition
The lecture begins by refactoring the previous example to demonstrate how event detection can be modularized into separate functions like onkeypress() and onfilechanged(). An event is an occurrence within a particular system or domain, with two meanings: something that happened and the corresponding detection in the computer world. An event captures "some" things from the actual occurrence, and multiple events may capture "one" occurrence. Probabilistic events may or may not relate to an actual occurrence, such as a fraud detection event on a banking transaction. Every event is represented by an event-object, which contains information describing the details of the particular event type, e.g., key press, file event.
🔑 Definition — Event: An occurrence within a particular system or domain, represented by an event-object that captures information about the occurrence.
Synchronous and Asynchronous Behavior
Using a coffee shop analogy, the lecture explains two types of behavior. If an order is completed, coffee ready, and pastry heated before the next order, that is synchronous behavior. Asynchronous operations can be started, and we can do something else before they are completed. There are observed events and deduced events. A situation is an event occurrence that requires a reaction.
🔑 Definition — Synchronous behavior: Operations are completed before the next operation can be started. 🔑 Definition — Asynchronous behavior: Operations can be started, and we can do something else before they are completed.
Events in Computing Systems
Examples of events in computing systems include interrupts and exceptions on a computer (e.g., Divide by zero), patient monitoring by sensors, car sensors alerting to oil or pressure situations, banking alerts, and road tolling. Event processing is computing that performs operations on events. Common event processing operations include reading, creating, transforming, and deleting events. The design, coding, and operation of applications that use events is called event-based programming or event-driven architecture.
💡 Why this matters: Without event-driven programming, we would have to poll for events, which is inefficient. Waiting for a single event is a blocking operation.
Why Event-Based Applications
Event-based applications are easier to scale. They are well suited to visual programming where multiple GUI elements and many sources of events exist. Event-driven design has a direct mapping to the real world and is deterministic — there is an exact mapping between a situation in the real world and its representation in the event processing system. At the same time, it is approximate — the event processing system provides an approximation to real world events.
Types of Events in Non-Real Time Applications
Types of events include: Mouse and keyboard events, secondary events of GUI elements, file events, message-based parallel and local communication, and network events.
Flower Delivery Example
The lecture presents a flower delivery example from the book. A consortium of flower stores has established an agreement with local independent van drivers to deliver flowers. When a store gets a flower delivery order:
- It creates a request broadcast to relevant drivers within a certain distance.
- A driver is assigned.
- The customer is notified that a delivery has been scheduled.
- The driver makes the pickup and delivery.
- The person receiving the flowers confirms the delivery time by signing on the driver's mobile device.
The system maintains a ranking for each driver based on on-time delivery. Each store has a profile that can include a constraint on the driver's ranking. The profile also indicates whether the store wants automatic assignment or wants to choose from several applications. A permanently weak driver has fewer than five assignments on all days active. An idle driver has at least one day of activity with no assignments. A consistently weak driver has assignments, when active, that are at least two standard deviations lower than the average. A consistently strong driver has daily assignments at least two standard deviations higher than the average. An improving driver has assignments that increase or stay the same day by day.
🔑 Definition — Permanently weak driver: A driver with fewer than five assignments on all the days on which the driver has been active. 🔑 Definition — Idle driver: A driver with at least one day of activity that had no assignments. 🔑 Definition — Consistently weak driver: A driver whose assignments, when active, are at least two standard deviations lower than the average assignments per driver on that day. 🔑 Definition — Consistently strong driver: A driver whose daily assignments are at least two standard deviations higher than the average number of assignments per driver on each day in question. 🔑 Definition — Improving driver: A driver whose assignments increase or stay the same day by day.
⭐ Key Takeaways
Events are occurrences that are detected and represented by event-objects in computing. The distinction between synchronous and asynchronous behavior is critical for understanding how event-driven systems work. Event-based programming is preferred over polling because it is non-blocking, scalable, and provides a direct mapping to real-world situations. Event processing involves reading, creating, transforming, and deleting events. The flower delivery example demonstrates a complex, real-world event-driven system with multiple event sources (store orders, driver assignments, delivery confirmations) and rules (driver rankings, store profiles, performance classifications).
🧠 Quick Revision Questions
- What is the difference between an event and an event-object?
- Give an example of a probabilistic event in a banking system.
- What is the difference between synchronous and asynchronous behavior?
- List four common event processing operations.
- In the flower delivery example, what defines a "consistently strong driver"?
📘 Lecture 3 — Event-Driven Architecture
📖 Overview: This lecture introduces event-driven architecture (EDA) as an alternative to request-response based applications. It explains the fundamental concepts of events, event producers and consumers, and how events decouple system components. The lecture also compares event-driven architecture with service-oriented architecture and provides practical definitions for different types of events and event processing.
🗂️ Topics Covered
The lecture begins by contrasting request-response and event-driven architectures, explaining how events are based on decoupling principles and are independent of producers and consumers. It then defines key concepts including event channels, event producers and consumers, raw and derived events, stateless event processing, and event streams. The lecture also covers types of event agents and event attributes, and provides a code example mapping event definitions to a C++ program. Finally, it discusses real-world examples of event producers and consumers in hardware, software, and human interaction contexts.
📝 Lecture Summary
Request-Response vs Event-Driven Architecture
Request-response based applications, like a web browser, send queries and updates using synchronous interactions. In contrast, events are based on the principle of decoupling. Events have already happened, whereas requests ask for something to happen. In request-response, the service requester (client) contacts the service provider (server). In event-driven architecture, the event producer sends events to the event consumer. A customer order can be represented as either an event or a request, each having different benefits.
An event has meaning independent of its producers and consumers, which is not the case for requests. Events are often one way (push events) and decouple producers from consumers. Events can be processed asynchronously, can have more than one consumer, each processing it differently, and may process a sequence of events over time. This reduces latency compared to the "pull" style of request-response.
💡 Why this matters: Understanding the distinction between events and requests is fundamental to choosing the right architectural pattern for different system requirements, especially when decoupling and asynchronous processing are needed.
Event Channels and Architecture Comparison
An event channel is a subscription mechanism for events that further decouples consumers and producers. It can even be an intermediate XML file to store events. Event-driven architecture (EDA) is an architectural style where components execute in response to receiving event notifications. Service-oriented architecture (SOA) is built from request-response and moves away from monolithic applications. These architectures share many similarities, and a component can often provide both modes of contacting it.
🔑 Definition — Event Channel: A subscription mechanism for events that further decouples consumers and producers.
Core Event Definitions
EVENT PRODUCER: An event producer is an entity at the edge of an event processing system that introduces events into the system.
EVENT CONSUMER: An event consumer is an entity at the edge of an event processing system that receives events from the system.
RAW EVENT: A raw event is an event that is introduced into an event processing system by an event producer.
DERIVED EVENT: A derived event is an event that is generated as a result of event processing that takes place inside an event processing system.
STATELESS EVENT PROCESSING: An event processing agent is said to be stateless if the way it processes one event does not influence the way it processes any subsequent events.
EVENT STREAM: An event stream (or stream) is a set of associated events. It is often a temporally totally ordered set (with a well-defined timestamp-based order to the events in the stream). A stream where all events must be of the same type is called a homogeneous event stream; a stream where events may be of different types is called a heterogeneous event stream.
Mapping Events to C++ Code
The lecture presents a C++ program and asks students to map event definitions to the code. The program demonstrates a file monitoring system that checks for keyboard input and file changes in a loop.
The program includes:
onkeypress()function that reads keyboard input (event consumer for keyboard events)onfileread()function that reads a file character by character and detects changesonfilechanged()function that outputs the changed character- A main loop that continuously checks for keyboard input and file changes
In this program:
- The keyboard and file system act as event producers
- The functions
onkeypress(),onfileread(), andonfilechanged()act as event consumers - The variable
filechangedserves as a simple event channel mechanism - The file content changes represent raw events
- The detection of a character change (
filechanged = 1) represents a derived event
Event Attributes and Types
An event type is a specification for a set of event objects that have the same semantic intent and same structure. Every event object is an instance of an event type. An event attribute is a component of the structure of an event, with each attribute having a name and a data type. Events can have attributes like occurrence/detection time, certainty, source, and location. Events can be composed, generalized, and specialized.
🔑 Definition — Event Type: A specification for a set of event objects that have the same semantic intent and same structure.
🔑 Definition — Event Attribute: A component of the structure of an event, where each attribute has a name and a data type.
Event producers can produce events that are:
- Hardware generated
- Software generated
- From human interaction
Event consumers have similar types. Examples include: locking or unlocking a door, raising or lowering a barrier, applying brakes on a vehicle, opening or closing a valve, controlling a railroad switch, and turning equipment on or off.
⭐ Key Takeaways
Events are fundamentally different from requests because events have already happened and are independent of their producers and consumers, while requests ask for something to happen. The key benefit of event-driven architecture is decoupling, which allows for asynchronous processing, multiple consumers, and reduced latency compared to request-response. Students must understand the distinction between raw events (introduced by producers) and derived events (generated by internal processing), as well as stateless versus stateful event processing. Event channels further enhance decoupling by providing subscription mechanisms between producers and consumers. Finally, event attributes including occurrence time, certainty, source, and location are essential for understanding real-world event objects in software systems.
🧠 Quick Revision Questions
- What are the three main differences between events and requests in software architecture?
- What is an event channel and what three purposes does it serve in event-driven architecture?
- Define and differentiate between raw events and derived events with an example for each.
- What does it mean for event processing to be stateless, and how does this differ from stateful processing?
- List four types of event attributes and give an example of how each might apply to a flower delivery event.
📘 Lecture 4 — Getting Introduced to C#
📖 Overview: This lecture introduces C# programming language, its history and relationship with .NET platform, and provides a comprehensive overview of C# features including type system, generics, preprocessor directives, and basic programming syntax. It covers how C# was developed as a clean-room implementation following Microsoft’s dispute with Sun over Java extensions, and demonstrates essential C# programming concepts through practical examples including console-based and GUI-based Hello World programs.
🗂️ Topics Covered
The lecture covers the historical context of C# development including the Java and J++ controversy, clean-room design concept, and the origin of C# name from “Cool” (C-like Object Oriented Language). It then explains how to obtain and install Visual Studio, presents notable features of C# including type safety, garbage collection, and property syntax, discusses the Common Type System distinguishing value types and reference types, demonstrates boxing/unboxing and generics concepts, covers pre-processor directives and XML documentation, and concludes with practical Hello World examples in both console and GUI formats.
📝 Lecture Summary
Getting Introduced to C#
The lecture begins with the historical context of C# development. Microsoft wanted to extend Java to communicate with COM (Component Object Model), but Sun Microsystems opposed this as it would make Java platform dependent. Microsoft then pursued a clean-room implementation of Java.
🔑 Definition — Clean-room design: The method of copying a design by reverse engineering and then recreating it without infringing any copyrights and trade secrets associated with the original design. It relies on independent invention but cannot circumvent patent restrictions.
The initial name for C# was “Cool”, which stood for “C-like Object Oriented Language”. C# design most directly reflects .NET (CLR) design, but C# is a language while .NET is the platform.
To get started, download Visual Studio from http://www.microsoft.com/visualstudio, choose “Visual Studio Express 2012 for Windows Desktop”, and install/register online. Create a new project by selecting File, New Project, Visual C#, Console Application.
Notable Features of C#
Key features include:
- No global variables or functions
- Locals cannot shadow global variables
- Strict boolean type exists
- Memory address pointers can only be used in specifically marked “unsafe” blocks and require permissions
- No instruction to “free” memory — only garbage collection
- Try-finally block is supported
- No “multiple inheritance” but interfaces are supported
- Operator overloading is allowed
- More type-safe (only integer widening allowed)
- Enumeration members are scoped
- Property syntax for getters and setters
- No checked exceptions
- Functional programming features like function objects and lambda expressions
💡 Why this matters: These features make C# a modern, type-safe, and memory-managed language that differs significantly from C++ in its approach to memory management and type safety.
Common Type System: Value Types vs Reference Types
The Common Type System of C# has value types and reference types.
Value types (int, float, char, System.DateTime, enum, struct):
- Instances do not have referential identity nor referential comparison semantics
- Derived from System.ValueType
- Can always be created, copied, and have a default value
Reference types (Object, System.String, System.Array):
- Have the notion of referential identity
- Default equality and inequality comparisons test for referential rather than structural equality unless overloaded (e.g., System.String)
- Not “always” possible to create an instance, copy an existing instance, or perform a value comparison on two instances, though specific types can provide such services by exposing a public constructor or implementing a corresponding interface
Boxing and Unboxing
Boxing stores a value type in a reference type.
📐 Example:
int foo = 42; // Value type
Object bar = foo; // foo is boxed to bar
int foo2 = (int)bar; // Unboxed back to value type
Generics in C#
Generics are like templates in C++. They allow type-safe data structures without committing to actual data types.
📐 Example:
public class GenericList<T>
{
void Add(T input) { }
}
class TestGenericList
{
private class ExampleClass { }
static void Main()
{
GenericList<int> list1 = new GenericList<int>();
GenericList<string> list2 = new GenericList<string>();
GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
}
}
Pre-processor Directives and Comments
Pre-processor directives like #if, #else, #endif, #region, #endregion are supported. Comments are written using // for single-line and /* */ for multi-line comments.
XML Documentation System
C# provides an XML documentation system for documenting code.
📐 Example:
public class Foo {
/** <summary>A summary of the method.</summary>
* <param name="firstparam">A description of the parameter.</param>
* <remarks>Remarks about the method.</remarks> */
public static void Bar(int firstparam) {}
}
Hello World Examples
Console-based Hello World:
using System;
class Program {
static void Main() {
Console.WriteLine("Hello world!");
}
}
The using System; clause uses System as a candidate prefix for types used in the source code. Console is a static class in the System namespace.
To prevent the console window from disappearing quickly in Visual Studio, add Console.ReadLine(); at the end.
GUI-based Hello World:
using System.Windows.Forms;
class Program
{
static void Main()
{
MessageBox.Show("Hello world!");
}
}
Interactive Hello World with input:
using System;
class InteractiveWelcome
{
public static void Main()
{
Console.Write("What is your name?: ");
Console.Write("Hello, {0}! ", Console.ReadLine());
Console.WriteLine("Welcome to the C# Station Tutorial!");
}
}
⭐ Key Takeaways
C# was developed as Microsoft's clean-room implementation after their dispute with Sun over Java extensions, with its original name being "Cool". The language features a strict type system with value types (derived from System.ValueType) and reference types, where value types always have default values and can be copied while reference types focus on referential identity. C# eliminates manual memory management by relying entirely on garbage collection, supports generics similar to C++ templates, and enforces type safety with no global variables or functions. The using directive simplifies coding by allowing namespace prefixes instead of full type names, and the Console class provides methods like WriteLine(), Write(), and ReadLine() for console I/O operations.
🧠 Quick Revision Questions
- What was the original name of C# and what did it stand for?
- What is the key difference between value types and reference types in C#?
- How does boxing work in C# and why would you use it?
- What is the purpose of the
using System;directive in C# programs? - Why does C# not allow a "free" instruction for memory, and what does it use instead?
📘 Lecture 5 — Chapter 5
📖 Overview: This lecture introduces fundamental C# data types including booleans, integers, floating-point numbers, and strings. It covers arrays (single-dimensional, jagged, and multi-dimensional), control flow statements (if/else, switch), all four loop types (while, do-while, for, foreach), and method declaration with parameter passing mechanisms. These concepts form the building blocks for writing structured C# programs.
🗂️ Topics Covered
Boolean types with examples, integer types in C#, floating-point and decimal types, System.String escape sequences and verbatim character, operators supported by C#, single-dimensional arrays, jagged arrays (array of arrays), multi-dimensional arrays, if-else control statements, switch statement with branching, while loops, for loops with break/continue, foreach loops, method declaration using attributes-modifiers-return-type-name-parameters syntax, parameter passing with ref, out, and params keywords, the "this" pointer, and the dot operator for accessing members.
📝 Lecture Summary
Boolean Types
Boolean types represent true/false values in C#. The bool keyword is used to declare boolean variables. In the example, bool content = true; and bool nocontent = false; are used with Console.WriteLine() to output boolean values. The format string {0} acts as a placeholder for the boolean value.
🔑 Definition — Boolean: A data type that can hold only two values: true or false.
📐 Syntax: bool variableName = true/false; → declares and initializes a boolean variable
📌 Example:
bool content = true;
bool nocontent = false;
Console.WriteLine("It is {0} that C# Station provides content.", content);
// Output: It is True that C# Station provides content.
Integer Types in C#
C# provides a range of integer types with different sizes and storage capacities. These include sbyte, byte, short, ushort, int, uint, long, and ulong. Each type has specific minimum and maximum values based on its bit size.
Floating Point and Decimal Types
C# offers floating-point types (float, double) and the decimal type for precise monetary calculations. float is 32-bit, double is 64-bit, and decimal is 128-bit with higher precision for financial applications.
System.String Escape Sequences
The System.String type supports escape sequences using the backslash \ character. Common escape sequences include \n (new line), \t (tab), and \\ (backslash). The verbatim character @ allows strings to be written exactly as typed, ignoring escape sequences.
🔑 Definition — Verbatim Character (@): A character that tells the compiler to treat the string literally, ignoring all escape sequences.
📌 Example: @"C:\Program Files" outputs C:\Program Files without interpreting \P as an escape sequence.
Operators in C#
C# supports a comprehensive set of operators including arithmetic (+, -, *, /, %), relational (<, >, ==, !=, <=, >=), logical (&&, ||, !), assignment (=, +=, -=), and bitwise operators.
Arrays in C#
Arrays in C# can be single-dimensional, jagged (array of arrays), or multi-dimensional. Arrays use a zero-based index and their size must be an integer type value.
Single-dimensional arrays: Declared as int[] myints = { 5, 10, 15 };
Jagged arrays: Array of arrays, declared as bool[][] mybools = new bool[2][]; where each inner array can have different sizes.
Multi-dimensional arrays: Declared as double[,] mydoubles = new double[2, 2]; with fixed dimensions.
🔑 Definition — Jagged Array: An array whose elements are themselves arrays, where each sub-array can have a different size.
📌 Example:
int[] myints = { 5, 10, 15 };
bool[][] mybools = new bool[2][];
mybools[0] = new bool[2];
mybools[1] = new bool[1];
double[,] mydoubles = new double[2, 2];
mydoubles[0, 0] = 3.147;
Console.WriteLine("mydoubles[0, 0]: {0}", mydoubles[0, 0]);
// Output: mydoubles[0, 0]: 3.147
💡 Why this matters: Understanding jagged versus multi-dimensional arrays is crucial for memory-efficient data structures where rows have varying lengths.
Control Statements: if/else
The if-else statement works similarly to C++. It evaluates boolean expressions and executes corresponding code blocks. Logical operators || (OR) and && (AND) are used for compound conditions.
📌 Example:
if (myint < 0 || myint == 0) {
Console.WriteLine("Number is less than or equal to zero.");
} else if (myint > 0 && myint <= 10) {
Console.WriteLine("Number is in range 1 to 10.");
} else {
Console.WriteLine("Number is greater than 10.");
}
Switch Statement and Branching
The switch statement can work with booleans, enums, integral types, and strings. It uses one of the following branching statements to exit: break, continue, goto, return, or throw.
🔑 Definition — Switch Statement: A control statement that selects one of many code blocks to execute based on the value of an expression.
While Loop
The while loop executes statements repeatedly as long as a boolean expression remains true. Syntax: while (<boolean expression>) { <statements> }
📌 Example:
int myint = 0;
while (myint < 10) {
Console.Write("{0} ", myint);
myint++;
}
// Output: 0 1 2 3 4 5 6 7 8 9
Do-While Loop
The do-while loop executes statements at least once before checking the condition. Syntax: do { <statements> } while (<boolean expression>);
For Loop
The for loop uses an initializer list, boolean expression, and iterator list. It supports break to exit early and continue to skip the current iteration.
📌 Example:
for (int i = 0; i < 20; i++) {
if (i == 10) break;
if (i % 2 == 0) continue;
Console.Write("{0} ", i);
}
// Output: 1 3 5 7 9
Foreach Loop
The foreach loop iterates over all elements in a collection or array. Syntax: foreach (<type> <iteration variable> in <list>) { <statements> }
📌 Example:
string[] names = {"Cheryl", "Joe", "Matt", "Robert"};
foreach (string person in names) {
Console.WriteLine("{0} ", person);
}
// Output: Cheryl Joe Matt Robert
Methods in C#
Methods are declared using the format: attributes modifiers return-type method-name(parameters) { statements }. Elements are accessed using the dot operator. Object variables are references to the original object, not the objects themselves.
🔑 Definition — Method: A code block that performs a specific task, declared with optional attributes, access modifiers, return type, method name, and parameters.
📌 Example:
class OneMethod {
public static void Main() {
string mychoice;
OneMethod om = new OneMethod();
mychoice = om.GetChoice();
}
string GetChoice() {
return "example";
}
}
The "this" Pointer
The "this" pointer in methods refers to the current object instance on which the method is called.
Parameter Passing: ref, out, and params
Parameters can be passed in three special ways:
- ref parameter: Passed by reference, allowing the method to modify the original variable
- out parameter: Used for returning values from the method
- params argument: Allows variable number of arguments
🔑 Definition — params: A keyword that allows a method to accept a variable number of arguments of a specified type.
📌 Example (params):
void ViewAddresses(params string[] names) {
foreach (string name in names) {
Console.WriteLine("Name: {0}", name);
}
}
⭐ Key Takeaways
The most critical concepts to remember are: C# provides multiple integer and floating-point types with different sizes and precision levels. Arrays can be single-dimensional, jagged (array of arrays with varying sizes), or multi-dimensional with fixed dimensions, all using zero-based indexing. Control flow includes if/else with logical operators, switch statements accepting booleans/enums/integrals/strings, and four loop types (while, do-while, for with break/continue, foreach for collections). Methods use the dot operator for access, and parameter passing supports ref (by reference), out (for return values), and params (variable arguments). Object variables are references that point to objects, not the objects themselves.
🧠 Quick Revision Questions
- What is the difference between a jagged array and a multi-dimensional array in C#?
- How does the
@verbatim character affect string interpretation? - What are the four types of loops available in C# and when would you use each?
- Explain the difference between
refandoutparameters in method declarations. - How does the
foreachloop work with arrays, and what is its syntax?
📘 Lecture 6 — Namespaces, Classes, Inheritance, Polymorphism, and Properties
📖 Overview: This lecture covers core C# object-oriented programming concepts including namespaces for code organization, class construction with constructors and destructors, inheritance hierarchies, polymorphism through virtual and override methods, and properties for encapsulated data access. These fundamentals are essential for building maintainable, object-oriented applications in C#.
🗂️ Topics Covered
Namespaces and nested namespaces with using directives; class declaration, constructors (ctor), destructors (dtor), instance and static members; inheritance with base and derived classes, constructor chaining using base(), and method overriding with new keyword; polymorphism with virtual and override keywords for runtime method dispatch; properties as accessor methods with get and set, including read-only and write-only implementations.
📝 Lecture Summary
Namespaces
Namespaces allow name reuse — for example, a Console class can reside in multiple libraries. Namespace declaration uses the namespace keyword followed by the namespace name and curly braces containing the code. Nested namespaces can be declared using dot notation (e.g., namespace testnamespace.tutorial) or by nesting namespace blocks. The using directive can rename a long namespace in the current file: using theexample = testnamespace.tutorial.myexample.
🔑 Definition — Namespace: A container for grouping related classes and preventing name conflicts between identical class names in different libraries.
Classes and Constructors
A class is declared with the keyword class followed by the class name, curly braces, and the class body. It has a constructor (ctor) to create objects — ctors do not return any values and initialize class members. A destructor (dtor) is called by the garbage collector for cleanup. Default ctors are written with no arguments when no ctor is explicitly defined. An initializer list can be used to call an alternate constructor: public outputclass() : this("Default Constructor String") { }. Multiple constructors can be defined (overloading).
Class Members: Instance vs Static
Instance members have a separate copy for every new object instance. Static members have only one copy shared across all instances. A static constructor initializes static members and is called only once, with no parameters. Static methods are called on the class itself (e.g., outputclass.staticprinter();). A class can contain: Constructors, Destructors, Fields, Methods, Properties, Indexers, Delegates, Events, and Nested Classes.
🔑 Definition — Static member: A class member that belongs to the class itself rather than any specific instance, with only one copy in memory.
📌 Example — Instance vs Static members:
Outputclass oc1 = new outputclass("outputclass1");
Outputclass oc2 = new outputclass("outputclass2");
// oc1.printstring and oc2.printstring access different instance variables
// Static members are called via the class: outputclass.staticprinter();
Inheritance
Inheritance introduces the concept of base classes and derived classes, declared as Derived : Base. C# supports only single inheritance (multiple interface inheritance is discussed later). A derived class "is a" base class — it is exactly the same as the base but more specialized. Base classes are automatically initialized before derived classes. Derived classes communicate with base classes using : base() at constructor time or base.x() later.
📌 Example — Inheritance chain:
Childclass child = new childclass();
child.print();
Output:
Parent Constructor.
Child Constructor.
I'm a Parent Class.
The new keyword is used to override methods (hiding base class methods). To call an overridden method of the base class, cast back to the base type: ((Parent)child).print().
🔑 Definition — Inheritance: A mechanism where a derived class inherits members and behavior from a base class, promoting code reuse and establishing an "is-a" relationship.
Polymorphism
Polymorphism means to invoke derived class methods through a base class reference during runtime. It is handy when a group of related objects is stored in an array/list and you invoke the same method on all of them. Polymorphic methods are declared with the virtual keyword in the base class and the override keyword in derived classes. Polymorphism requires the method signatures to be the same. Because of inheritance, derived classes can be treated as the base class; because of polymorphism, the derived class methods are still called.
📌 Example — Polymorphism with drawing objects:
DrawingObject[] dobj = new DrawingObject[4];
dobj[0] = new Line();
dobj[1] = new Circle();
dobj[2] = new Square();
dobj[3] = new DrawingObject();
foreach (DrawingObject drawobj in dobj)
{
drawobj.Draw();
}
Output:
I'm a Line.
I'm a Circle.
I'm a Square.
I'm just a generic drawing object.
🔑 Definition — Polymorphism: The ability of a base class reference to call derived class methods at runtime using virtual and override keywords, enabling different behaviors for the same method call.
Properties
Properties allow protected reads and writes to a field of a class, enabling access like fields while maintaining encapsulation. Other languages require custom getter and setter methods, but C# provides a dedicated language feature. Properties use get accessors (which return values) and set accessors (which use the value keyword). Read-only properties have only a get accessor (no set). Write-only properties have only a set accessor (no get).
🔑 Definition — Property: A member that provides a flexible mechanism to read, write, or compute the value of a private field, accessed like a public field but with encapsulated get/set logic.
📌 Example — Property with get and set:
public int ID
{
get { return m_id; }
set { m_id = value; }
}
// Usage:
cust.ID = 1; // Calls set accessor
Console.WriteLine(cust.ID); // Calls get accessor
💡 Why this matters: Properties provide controlled access to private fields, allowing validation or logic changes without breaking external code, unlike public fields which expose implementation details.
⭐ Key Takeaways
Namespaces allow name reuse and code organization, with the using directive enabling namespace aliasing. Classes use constructors to initialize objects and destructors for cleanup; instance members are per-object while static members are shared. Inheritance with : base enables base class initialization and communication through constructor chaining and the base keyword. Polymorphism using virtual and override allows runtime method dispatch through base class references, essential for flexible, extensible designs. Properties replace getter/setter methods with field-like syntax, supporting read-only and write-only patterns for controlled data access.
🧠 Quick Revision Questions
- What keyword is used to declare a namespace, and how can nested namespaces be accessed from within the parent namespace?
- What is the difference between instance members and static members in a C# class?
- How does the
newkeyword differ fromoverridein the context of inheritance? - What output is produced when a
Lineobject is cast toDrawingObjectand itsDraw()method is called, assuming proper polymorphism? - How would you implement a read-only property in C#, and what accessor would you omit?
📘 Lecture 7 — Auto-Implemented Properties, Indexers, Structs, and Interfaces
📖 Overview: This lecture explores four key C# features that enhance object-oriented programming. Auto-implemented properties simplify property declarations, indexers allow classes to be accessed like arrays, structs provide value-type alternatives to classes, and interfaces define contracts for plug-and-play architecture. Understanding these concepts is essential for writing efficient, flexible C# code.
🗂️ Topics Covered
The lecture begins with auto-implemented properties as a simplified syntax for common property patterns. It then covers indexers, including single-parameter and overloaded indexers with multiple parameter types. Next, structs are introduced as value-types with property and method support, along with object initializer syntax. Finally, interfaces are explored as contracts with method signatures only, supporting inheritance and polymorphism.
📝 Lecture Summary
Auto-Implemented Properties
Auto-implemented properties provide a simplified syntax for the common case where properties simply get and set a backing store. The compiler automatically generates the backing store, eliminating the need for explicit private fields. Properties maintain the same idea of getters and setters but with cleaner code.
🔑 Definition — Auto-implemented property: A property declaration where the compiler automatically creates a hidden backing field, indicated by { get; set; } syntax.
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
}
📌 Example: The Customer class uses auto-implemented properties for ID and Name. In the Main method, a Customer object is created and its properties are set directly:
Customer cust = new Customer();
cust.ID = 1;
cust.Name = "Amelio Rosales";
Console.WriteLine("ID: {0}, Name: {1}", cust.ID, cust.Name);
💡 Why this matters: Auto-implemented properties reduce boilerplate code while maintaining the encapsulation benefits of traditional properties.
Indexers
An indexer enables a class to be treated like an array for internal data access. It is implemented using the this keyword with square bracket syntax and looks similar to a property implementation. Indexers can have any number of parameters of various types (integers, strings, enums) and can be overloaded.
🔑 Definition — Indexer: A special type of property that allows objects to be indexed like arrays, using the this keyword with parameters in square brackets.
📐 Formula: public returnType this[parameterType parameterName] { get { } set { } }
📌 Example: A simple integer indexer for a string array:
class IntIndexer
{
private string[] myData;
public IntIndexer(int size)
{
myData = new string[size];
for (int i = 0; i < size; i++)
myData[i] = "empty";
}
public string this[int pos]
{
get { return myData[pos]; }
set { myData[pos] = value; }
}
}
Usage: myInd[9] = "Some Value"; — This sets index position 9 to "Some Value".
Overloaded Indexers
Indexers can be overloaded with different parameter types and counts. This allows accessing data through different keys.
📐 Formula: public string this[string data] { get { } set { } }
📌 Example: An overloaded indexer using string parameter:
public string this[string data]
{
get
{
int count = 0;
for (int i = 0; i < arrSize; i++)
if (myData[i] == data) count++;
return count.ToString();
}
set
{
for (int i = 0; i < arrSize; i++)
if (myData[i] == data) myData[i] = value;
}
}
Usage: myInd["empty"] = "no value"; — Replaces all "empty" entries with "no value". Then myInd["no value"] returns the count of "no value" entries.
Multi-Parameter Indexers
Indexers with multiple parameters follow this syntax:
public object this[int param1, ..., int paramN]
{
get { /* process and return some class data */ }
set { /* process and assign some class data */ }
}
Structs
A struct is a value-type, whereas a class is a reference-type. Value types hold their value in memory where they are declared, while reference types hold a reference to an object in memory. When copying a class, C# creates a new copy of the reference; with structs, the actual data is copied directly.
🔑 Key differences: Structs cannot have destructors, cannot have implementation inheritance (but can have interface inheritance). Many built-in types are structs, such as System.Int32 (C# int). The syntax of struct and class are very similar.
📌 Example: A Rectangle struct with properties:
struct Rectangle
{
private int m_width;
public int Width
{
get { return m_width; }
set { m_width = value; }
}
private int m_height;
public int Height
{
get { return m_height; }
set { m_height = value; }
}
}
Usage:
Rectangle rect1 = new Rectangle();
rect1.Width = 1;
rect1.Height = 3;
Console.WriteLine("rect1: {0}:{1}", rect1.Width, rect1.Height); // Output: 1:3
🔑 Object initializer syntax: A way to initialize a struct without calling a constructor explicitly:
Rectangle rect11 = new Rectangle
{
Width = 1,
Height = 3
};
Interfaces
An interface is declared like a class but has no implementation — only declarations of events, indexers, methods, and/or properties. Classes inherit interfaces and provide the real implementation. Interfaces are essential for plug-n-play architectures where components can be interchanged at will. The interface forces each component to expose specific public members.
🔑 Definition — Interface contract: When a class implements an interface, it guarantees it has all the methods defined in that interface. For example, a class implementing IDisposable guarantees it has a Dispose() method.
📐 Formula for interface definition:
interface IMyInterface
{
void MethodToImplement();
}
📌 Example: Interface implementation:
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
}
Interface Inheritance
Interfaces can inherit from other interfaces:
interface IParentInterface
{
void ParentInterfaceMethod();
}
interface IMyInterface : IParentInterface
{
void MethodToImplement();
}
A class implementing IMyInterface must implement both methods. Polymorphism can be achieved using interfaces.
⭐ Key Takeaways
Auto-implemented properties ({ get; set; }) simplify property declarations by having the compiler generate the backing store automatically, reducing code while preserving encapsulation. Indexers use this[key] syntax to make classes accessible like arrays, supporting overloading with different parameter types and counts for flexible data access. Structs are value-types that hold data directly in memory (unlike reference-type classes), cannot have destructors or implementation inheritance, and support object initializer syntax for simplified construction. Interfaces define contracts with method signatures only (using "I" prefix convention), enabling plug-and-play architectures where implementing classes must provide all defined members, and they support both inheritance and polymorphism.
🧠 Quick Revision Questions
- What is the key difference between a struct and a class in terms of memory handling?
- How do you declare an indexer that uses a string parameter instead of an integer?
- Can a struct have implementation inheritance? What kind of inheritance can it have?
- What happens if a class implementing an interface does not provide an implementation for one of the interface methods?
- Write the syntax for an object initializer that sets Width to 5 and Height to 10 in a Rectangle struct.
📘 Lecture 8 — Delegates, Events, and Exception Handling
📖 Overview: This lecture introduces three fundamental concepts in C# programming: delegates (type-safe function pointers), events (the publisher-subscriber pattern), and exception handling (managing runtime errors). Understanding these concepts is essential for writing flexible, responsive, and robust Windows applications.
🗂️ Topics Covered
The lecture covers delegates as references to methods, demonstrated through a sorting algorithm with a custom comparison delegate; C# events as class members that notify registered methods when triggered, shown with a Windows Forms example; and exception handling using try-catch blocks to manage runtime errors gracefully, including multiple catch handlers for different exception types.
📝 Lecture Summary
Delegates
A delegate is a reference to a method, similar to function pointers in other languages. Methods are algorithms that operate on data. Sometimes the data needs a special operation, e.g., a comparator in a sorting routine. Without delegates, you would need a bad solution using if-then-else for all types. Two good solutions exist: (1) implement a comparator interface in all types and pass an instance, or (2) use delegates (references to functions) and pass a comparator delegate to the sorting algorithm.
🔑 Definition — delegate: A type-safe object that holds a reference to a method (or multiple methods), allowing methods to be passed as parameters.
📐 Declaration syntax: public delegate int Comparer(object obj1, object obj2); → This creates a delegate type that can reference any method that takes two objects and returns an integer.
📌 Example: Full delegate implementation for sorting names by first name:
- Define
Comparerdelegate type - Create
Nameclass withcomparefirstnamesstatic method matching the delegate signature - In
SimpleDelegateclass, instantiate the delegate:Comparer cmp = new Comparer(Name.comparefirstnames); - Pass delegate to sort method:
sd.Sort(cmp); - The
Sortmethod uses the delegate:if (compare(names[i], names[j]) > 0)to compare and swap elements - Output shows names sorted alphabetically by first name
💡 Why this matters: Delegates enable callback methods and allow algorithms to be decoupled from the data they operate on, making code more reusable and flexible.
Events
A C# event is a class member that is activated whenever the event it was designed for occurs (fires). Anyone interested in the event can register and be notified as soon as the event fires. At the time an event fires, methods registered with the event will be invoked.
Events and delegates work hand in hand. Any class may register one of its methods with the event through a delegate, which specifies the signature of the method registered for the event. The delegate may be pre-defined (.NET delegates) or custom. You assign the delegate to the event, which effectively registers the method that will be called when the event fires.
🔑 Definition — event: A class member that provides a notification mechanism, allowing objects to subscribe and be notified when something occurs.
📐 Declaration syntax: public event StartDelegate startEvent; → Declares an event of delegate type StartDelegate
📌 Example: Windows Forms event demonstration:
- Define custom delegate:
public delegate void StartDelegate(); - Create
EventDemoclass inheriting fromForm - Declare custom event:
public event StartDelegate startEvent; - Create a button with Click event:
clickMe.Click += new EventHandler(onClickMeClicked); - Register with custom event:
startEvent += new StartDelegate(onStartEvent); - Fire custom event:
startEvent();(callsonStartEvent) - Result: Two message boxes appear — one at startup ("I Just Started!") and one on button click ("You Clicked My Button!")
💡 Why this matters: Events enable the observer pattern where multiple objects can react to state changes without the event source knowing about the subscribers, essential for GUI applications.
Exception Handling
There can be unforeseen errors in the program. There are normal errors and exceptional errors, e.g., File I/O error, system out of memory, null pointer exception. Exceptions are "thrown". They are derived from System.Exception class. They have a Message property, contain a StackTrace, and a ToString method. Identifying which exceptions you'll need to handle depends on the routine you're writing, e.g., System.IO.File.OpenRead() could throw SecurityException, ArgumentException, ArgumentNullException, PathTooLongException, DirectoryNotFoundException, UnauthorizedAccessException, FileNotFoundException, or NotSupportedException.
🔑 Definition — exception: An object that represents an error or unexpected condition during program execution, derived from System.Exception.
📐 Syntax:
try
{
// code that might throw
}
catch (Exception ex)
{
// handle exception
}
📌 Example: Multiple catch handlers:
try
{
File.OpenRead("nonexistentfile");
}
catch (FileNotFoundException fnfEx)
{
Console.WriteLine(fnfEx.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
The first handler that matches catches the exception. Otherwise, the exception bubbles up the caller stack until someone handles it.
💡 Why this matters: Proper exception handling prevents program crashes, provides meaningful error messages, and allows graceful recovery from runtime errors.
⭐ Key Takeaways
Delegates are type-safe function references that allow methods to be passed as parameters, making algorithms like sorting more flexible and reusable. Events build on delegates to implement the publisher-subscriber pattern, where objects can register interest in notifications and be called when events fire — essential for GUI programming. Exception handling uses try-catch blocks to manage runtime errors, with more specific catch handlers evaluated first and exceptions bubbling up the call stack if unhandled. All exceptions derive from System.Exception and provide detailed information through properties like Message and StackTrace. The += operator is used both for registering delegates with events and for adding multiple handlers to a single event.
🧠 Quick Revision Questions
- What is a delegate and how does it differ from a regular method call?
- How do you declare a custom event in C# and what role does a delegate play in events?
- What happens when an exception is not caught in the current method?
- Why would you use multiple catch blocks for a single try block?
- What is the
+=syntax doing when used with events likeclickMe.Click += new EventHandler(...)?
📘 Lecture 9 — Exception Handling, Attributes, Enums, Operator Overloading, and Generic Collections
📖 Overview: This lecture covers several important C# programming concepts, including resource cleanup using the finally block, adding declarative information to code with attributes, working with strongly typed constants using enums, extending operators for custom types through operator overloading, and using type-safe generic collections. These concepts are fundamental for writing robust, maintainable, and type-safe C# applications.
🗂️ Topics Covered
The lecture begins with exception handling using the finally block for resource cleanup, then moves to attributes which add metadata to programs. It covers enums as strongly typed constants with practical usage examples, followed by operator overloading for custom types. The lecture concludes with generic collections including List and Dictionary for type-safe data management.
📝 Lecture Summary
Exception Handling and the Finally Block
Exception can leave your program in an inconsistent state by not releasing resources or doing some other type of cleanup. Sometimes you need to perform clean up actions whether or not your program succeeds. These are good candidates for using a finally block, for example, a filestream must be closed. The finally block ensures cleanup code executes regardless of whether an exception occurred.
Using System;
Using System.IO;
Class finallydemo
{
Static void Main(string[] args)
{
Filestream outstream = null;
Filestream instream = null;
Try
{
Outstream = File.openwrite("destinationfile.txt");
Instream = File.openread("bogusinputfile.txt");
}
Catch (Exception ex)
{
Console.writeline(ex.tostring());
}
Finally
{
If (outstream != null)
{
Outstream.Close();
Console.writeline("outstream closed.");
}
If (instream != null)
{
Instream.Close();
Console.writeline("instream closed.");
}
}
}
}
The alternate would be to duplicate code after catch and in catch. But finally is a neat cleanup solution.
Attributes
Attributes add declarative information to your programs. They are used for various purposes during runtime. They can be used at design time by application development tools. For example, DllImportAttribute allows a program to communicate with Win32 libraries. ObsoleteAttribute causes a compile-time warning to appear. Such things would be difficult to accomplish with normal code. Attributes add metadata to your programs.
When your C# program is compiled, it creates a file called an assembly, which is normally an executable or DLL library. Assemblies are self-describing because they have metadata. Via reflection, a program's attributes can be retrieved from its assembly metadata. Attributes are classes that can be written in C# and are used to decorate your code with declarative information.
Attributes are generally applied physically in front of type and type member declarations. They are declared with square brackets, "[" and "]" surrounding the attribute such as "[obsoleteattribute]". The "Attribute" part of the attribute name is optional, i.e. "[Obsolete]" is correct as well. Parameter lists are also possible.
Using System;
Class basicattributedemo
{
[Obsolete]
Public void myfirstdeprecatedmethod()
{
Console.writeline("Called myfirstdeprecatedmethod().");
}
[obsoleteattribute]
Public void myseconddeprecatedmethod()
{
Console.writeline("Called myseconddeprecatedmethod().");
}
[Obsolete("You shouldn't use this method anymore.")]
Public void mythirddeprecatedmethod()
{
Console.writeline("Called mythirddeprecatedmethod().");
}
[stathread]
Static void Main(string[] args)
{
Basicattributedemo attrdemo = new basicattributedemo();
Attrdemo.myfirstdeprecatedmethod();
Attrdemo.myseconddeprecatedmethod();
Attrdemo.mythirddeprecatedmethod();
}
}
When you compile this program you'll see compilation warnings indicating that the methods are obsolete. Stathread is a common attribute that stands for Single Threaded Apartment model which is used for communicating with unmanaged COM.
Attribute parameters can be either positional parameters or named parameters. Usually named parameters with optional stuff, but positional can be optional as well. For example, [Obsolete("You shouldn't use this method anymore.", true)] will give an error instead of warning. The attribute DllImportAttribute has both positional and named parameters: [dllimport("User32.dll", entrypoint="messagebox")]. Positional parameters come before named parameters. There is no order requirement on named parameters.
Using System;
[assembly: clscompliant(true)]
Public class attributetargetdemo
{
Public void nonclscompliantmethod(uint nclsparam)
{
Console.writeline("Called nonclscompliantmethod().");
}
[stathread]
Static void Main(string[] args)
{
uint myuint = 0;
Attributetargetdemo tgtdemo = new attributetargetdemo();
Tgtdemo.nonclscompliantmethod(myuint);
}
}
We rarely write new attributes but we use them extensively.
Enums
Enums (or enumerations) are strongly typed constants. They are unique types that allow you to assign symbolic names to integral values. An enum of one type may not be implicitly assigned to an enum of another type (even though the underlying value of their members is the same). All assignments between different enum types and integral types require an explicit cast. It allows you to work with integral values, but using a meaningful name, like North, South, East, and West instead of integers 0, 1, 2, and 3. The C# type enum inherits the Base Class Library (BCL) type Enum.
🔑 Definition — Enum: A distinct type consisting of a set of named constants called the enumerator list.
Using System;
// declares the enum
Public enum Volume
{
Low,
Medium,
High
}
// demonstrates how to use the enum
Class enumswitch
{
Static void Main()
{
Volume myvolume = Volume.Medium;
Switch (myvolume)
{
Case Volume.Low:
Console.writeline("The volume has been turned Down.");
Break;
Case Volume.Medium:
Console.writeline("The volume is in the middle.");
Break;
Case Volume.High:
Console.writeline("The volume has been turned up.");
Break;
}
Console.readline();
}
}
Default underlying type of an enum is int. It can be changed by specifying a base. Valid base types include byte, sbyte, short, ushort, int, uint, long, and ulong. Default value of the first member is 0. You can assign any member any value. If it is unassigned it gets +1 the value of its predecessor.
Public enum Volume : byte
{
Low = 1,
Medium,
High
}
To convert user input to enum:
String volstring = Console.readline();
Int volint = Int32.Parse(volstring);
Volume myvolume = (Volume)volint;
Enum inherits from System.Enum in base class library.
To get a list of member names from Volume enum:
Foreach (string volume in Enum.getnames(typeof(Volume)))
{
Console.writeline("Volume Member: {0}\n Value: {1}",
Volume, (byte)Enum.Parse(typeof(Volume), volume));
}
To enumerate values:
Foreach (byte val in Enum.getvalues(typeof(Volume)))
{
Console.writeline("Volume Value: {0}\n Member: {1}",
Val, Enum.getname(typeof(Volume), val));
}
Operator Overloading
You can add operators to your own types, for example, a Matrix type can have an add and a dot product operator.
Matrix result = mat1.Add(mat2); // instance
Matrix result = Matrix.Add(mat1, mat2); // static
Matrix result = mat1.dotproduct(mat2).dotproduct(mat3);
Matrix result = mat1 + mat2; // operator overload
Matrix result = mat1 * mat2 * mat3 * mat4; // operator overload
It should not be used when its not natural to be used. Its implementation syntax is:
Public static Matrix operator *(Matrix mat1, Matrix mat2)
{
// dot product implementation
}
Here is a complete example:
Using System;
Class Matrix
{
Public const int dimsize = 3;
Private double[,] m_matrix = new double[dimsize, dimsize];
Public double this[int x, int y]
{
Get { return m_matrix[x, y]; }
Set { m_matrix[x, y] = value; }
}
Public static Matrix operator +(Matrix mat1, Matrix mat2)
{
Matrix newmatrix = new Matrix();
For (int x = 0; x < dimsize; x++)
For (int y = 0; y < dimsize; y++)
Newmatrix[x, y] = mat1[x, y] + mat2[x, y];
Return newmatrix;
}
}
🔑 Definition — Operator Overloading: Defining how built-in operators (like +, -, *) work with user-defined types.
📐 Rule: Overloaded operators must be static. They must be declared in the class for which the operator is defined. It is required that matching operators are both defined, e.g. == and !=, so that the behavior is consistent.
Access modifiers on types and assemblies provide encapsulation. They are: private, protected, internal, protected internal, and public.
Generic Collections
There is a very useful List collection. It is better than ArrayList of objects in earlier .NET versions. Another useful collection is Dictionary<TKey, TValue> vs. a non-generic Hashtable of objects. We write using System.Collections.Generic to include these collections.
List<int> myints = new List<int>();
Myints.Add(1);
Myints.Add(2);
Myints.Add(3);
For (int i = 0; i < myints.Count; i++)
{
Console.writeline("myints: {0}", myints[i]);
}
To work with custom objects in dictionaries:
Public class Customer
{
Public Customer(int id, string name)
{
ID = id;
Name = name;
}
Public int ID { Get; Set; }
Public string Name { Get; Set; }
}
Dictionary<int, Customer> customers = new Dictionary<int, Customer>();
Customer cust1 = new Customer(1, "Cust 1");
Customer cust2 = new Customer(2, "Cust 2");
Customer cust3 = new Customer(3, "Cust 3");
Customers.Add(cust1.ID, cust1);
Customers.Add(cust2.ID, cust2);
Customers.Add(cust3.ID, cust3);
Foreach (keyvaluepair<int, Customer> custkeyval in customers)
{
Console.writeline(
"Customer ID: {0}, Name: {1}",
Custkeyval.Key,
Custkeyval.Value.Name);
}
💡 Why this matters: Generic collections provide type safety at compile time, eliminating the need for runtime type checking and boxing/unboxing, making code faster and safer.
⭐ Key Takeaways
The finally block is essential for resource cleanup code that must execute whether an exception occurs or not, particularly for file streams and other unmanaged resources. Attributes add metadata to assemblies and can be retrieved via reflection, with the Attribute suffix being optional in their declaration. Enums provide strongly typed constants with an underlying integral type (default int), and values can be iterated using Enum.GetNames and Enum.GetValues. Operator overloading must use static methods within the class, and matching operators (like == and !=) must be defined together. Generic collections like List<T> and Dictionary<TKey, TValue> provide type-safe alternatives to non-generic collections, requiring the System.Collections.Generic namespace.
🧠 Quick Revision Questions
-
What is the purpose of the finally block in exception handling, and why is it preferred over duplicating cleanup code?
-
How do you declare an attribute in C#, and what is the significance of the "Attribute" suffix being optional?
-
What is the default underlying type of an enum, and how can you change it to a different integral type?
-
What are the requirements for implementing operator overloading in C# (specifically regarding static modifier and matching operators)?
-
What namespace is required for using generic collections like List<T> and Dictionary<TKey, TValue>, and what advantage do they offer over non-generic collections?
📘 Lecture 10 — Anonymous Methods, Debugging, XML Parsing & Writing
📖 Overview: This lecture covers three distinct topics: anonymous methods in C# for simplifying event handling, debugging techniques in Visual Studio, and comprehensive XML processing including reading, writing, and updating XML documents using both XmlReader and XmlDocument classes. These skills are essential for writing efficient code, troubleshooting applications, and handling data exchange formats.
🗂️ Topics Covered
Anonymous methods and their use with delegates and events, including parameter handling; nullable value types with the ? operator and null-coalescing operator ??; debugging in Visual Studio using breakpoints, watch windows, and conditional breakpoints; XML structure and syntax; reading XML with XmlReader and XmlDocument classes; XPath querying with SelectSingleNode and SelectNodes; writing XML with XmlWriter and XmlDocument; updating XML documents using XmlDocument.
📝 Lecture Summary
Anonymous Methods
Anonymous methods are methods without a name, used directly with delegates and events to reduce code. Instead of declaring a delegate, writing a separate method, declaring an event based on the delegate, and hooking the handler up, you simply use the keyword delegate followed by the method body directly in the event subscription. This results in much less code. Parameters can be skipped when not needed.
🔑 Definition — Anonymous Method: A method without a name that is hooked up directly to an event using the delegate keyword followed by the method body.
📌 Example (without parameters):
Button btnhello = new Button();
btnhello.Text = "Hello";
btnhello.Click += delegate { MessageBox.Show("Hello"); };
📌 Example (with parameters):
Button btngoodbye = new Button();
btngoodbye.Text = "Goodbye";
btngoodbye.Click += delegate(object sender, EventArgs e)
{
string message = (sender as Button).Text;
MessageBox.Show(message);
};
Nullable Types
You can create nullable value types by appending a question mark (?) to a type name. This allows value types (like int, DateTime) to hold null values. The null-coalescing operator (??) provides a shorthand for checking if a nullable type is null and assigning a default value.
🔑 Definition — Nullable Type: A value type that can also be assigned null, created by appending ? to the type name (e.g., int?).
📐 Formula: variable ?? defaultValue → If variable is not null, use its value; otherwise use defaultValue.
📌 Example:
int? unitsInStock = 5;
DateTime? startDate = DateTime.Now;
startDate = null;
// Without null-coalescing operator
int availableUnits;
if (unitsInStock == null)
availableUnits = 0;
else
availableUnits = (int)unitsInStock;
// With null-coalescing operator (equivalent)
int availableUnits = unitsInStock ?? 0;
💡 Why this matters: Nullable types and the ?? operator provide clean, concise code for handling potentially missing data without complex if-else structures.
Debugging in Visual Studio
Debugging helps find and fix errors by printing output or using breakpoints that stop program execution. Press F5 to run with debugging; execution will stop at any breakpoint. You can hover over variables to see their current values. The Locals, Watch, Call Stack, and Immediate Window provide different information about the program state. Changes made during debugging affect the current execution. Conditional breakpoints can have a hit count and conditions for when to stop execution.
XML Basics
XML (Extensible Markup Language) is widely used for exchanging data. It is readable for both humans and machines. It is a stricter version of HTML. XML is made of tags, attributes, and values.
📌 Example of simple XML:
<users>
<user name="John Doe" age="42" />
<user name="Jane Doe" age="39" />
</users>
📌 Example of larger XML (Euro exchange rates):
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
<gesmes:subject>Reference rates</gesmes:subject>
<gesmes:Sender>
<gesmes:name>European Central Bank</gesmes:name>
</gesmes:Sender>
<Cube>
<Cube time="2012-12-18">
<Cube currency="USD" rate="1.3178"/>
<Cube currency="JPY" rate="110.53"/>
<!-- more currency cubes -->
</Cube>
</Cube>
</gesmes:Envelope>
Reading XML with XmlReader
The XmlReader class provides a fast, forward-only, read-only cursor for processing XML. It reads one element at a time and uses less memory than XmlDocument.
🔑 Definition — XmlReader: A fast, read-only, forward-only cursor that processes XML one element at a time.
📌 Example (reading Euro exchange rates):
XmlReader xmlreader = XmlReader.Create("http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml");
while (xmlreader.Read())
{
if ((xmlreader.NodeType == XmlNodeType.Element) && (xmlreader.Name == "Cube"))
{
if (xmlreader.HasAttributes)
Console.WriteLine(xmlreader.GetAttribute("currency") + ": " + xmlreader.GetAttribute("rate"));
}
}
Reading XML with XmlDocument
The XmlDocument class reads the entire document into memory, allowing forward and backward navigation and XPath searches. DocumentElement is the root element, and ChildNodes is the set of children of any node.
🔑 Definition — XPath: A cross-platform XML Query language used to navigate and select nodes in an XML document.
📌 Example (same data using XmlDocument):
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load("http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml");
foreach (XmlNode xmlnode in xmldoc.DocumentElement.ChildNodes[2].ChildNodes[0].ChildNodes)
Console.WriteLine(xmlnode.Attributes["currency"].Value + ": " + xmlnode.Attributes["rate"].Value);
📌 Example (using XPath with SelectSingleNode for RSS title):
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load("http://rss.cnn.com/rss/edition_world.rss");
XmlNode titlenode = xmldoc.SelectSingleNode("//rss/channel/title");
if (titlenode != null)
Console.WriteLine(titlenode.InnerText);
📌 Example (using XPath with SelectNodes for multiple items):
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load("http://rss.cnn.com/rss/edition_world.rss");
XmlNodeList itemnodes = xmldoc.SelectNodes("//rss/channel/item");
foreach (XmlNode itemnode in itemnodes)
{
XmlNode titlenode = itemnode.SelectSingleNode("title");
XmlNode datenode = itemnode.SelectSingleNode("pubDate");
if ((titlenode != null) && (datenode != null))
Console.WriteLine(datenode.InnerText + ": " + titlenode.InnerText);
}
💡 Why this matters: XmlReader is best for simple, forward-only processing with minimal memory, while XmlDocument is better for complex navigation, updates, and XPath queries.
Writing XML with XmlWriter
The XmlWriter class creates XML documents sequentially. It provides methods like WriteStartDocument(), WriteStartElement(), WriteAttributeString(), WriteString(), WriteEndElement(), and WriteEndDocument().
📌 Example:
XmlWriter xmlwriter = XmlWriter.Create("test.xml");
xmlwriter.WriteStartDocument();
xmlwriter.WriteStartElement("users");
xmlwriter.WriteStartElement("user");
xmlwriter.WriteAttributeString("age", "42");
xmlwriter.WriteString("John Doe");
xmlwriter.WriteEndElement();
xmlwriter.WriteStartElement("user");
xmlwriter.WriteAttributeString("age", "39");
xmlwriter.WriteString("Jane Doe");
xmlwriter.WriteEndDocument();
xmlwriter.Close();
This produces:
<users>
<user age="42">John Doe</user>
<user age="39">Jane Doe</user>
</users>
Writing/Updating XML with XmlDocument
Writing with XmlDocument uses methods like CreateElement(), CreateAttribute(), AppendChild(), and Save(). This approach is especially useful for updates.
📌 Example (creating XML):
XmlDocument xmldoc = new XmlDocument();
XmlNode rootnode = xmldoc.CreateElement("users");
xmldoc.AppendChild(rootnode);
XmlNode usernode = xmldoc.CreateElement("user");
XmlAttribute attribute = xmldoc.CreateAttribute("age");
attribute.Value = "42";
usernode.Attributes.Append(attribute);
usernode.InnerText = "John Doe";
rootnode.AppendChild(usernode);
// ... more nodes
xmldoc.Save("test-doc.xml");
📌 Example (updating XML – incrementing ages):
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load("test-doc.xml");
XmlNodeList usernodes = xmldoc.SelectNodes("//users/user");
foreach (XmlNode usernode in usernodes)
{
int age = int.Parse(usernode.Attributes["age"].Value);
usernode.Attributes["age"].Value = (age + 1).ToString();
}
xmldoc.Save("test-doc.xml");
💡 Why this matters: Updating XML with XmlDocument is much easier than with XmlReader/XmlWriter combination, as XmlDocument allows direct modification of nodes in memory.
⭐ Key Takeaways
Anonymous methods allow you to write event handlers inline using the delegate keyword, eliminating the need for separate named methods and reducing code. Nullable types (int?, DateTime?) enable value types to hold null, and the ?? operator provides a concise default value assignment. Debugging in Visual Studio with breakpoints, conditional breakpoints, and inspection windows (Locals, Watch, Call Stack, Immediate Window) is essential for finding and fixing bugs. For XML processing, use XmlReader for fast, forward-only reading with minimal memory, and XmlDocument for bidirectional navigation, XPath queries, and document updates. Writing XML can be done with XmlWriter for sequential creation or XmlDocument for more flexible, in-memory construction and modification.
🧠 Quick Revision Questions
- What is an anonymous method and how is it declared in C# event handling?
- How do you create a nullable integer variable, and what does the
??operator do? - What is the difference between XmlReader and XmlDocument when reading XML files?
- What XPath method would you use to select a single node, and what method would you use to select multiple nodes?
- How would you update all "age" attributes in an XML document by incrementing them by 1 using XmlDocument?
📘 Lecture 11 — Extension Methods, File I/O, and Reflection in C#
📖 Overview: This lecture covers three important C# programming concepts: extension methods for adding functionality to existing types, file and directory handling using the System.IO namespace, and reflection for examining type metadata at runtime. These techniques are essential for writing flexible, maintainable code and building systems that can adapt to dynamic requirements.
🗂️ Topics Covered
The lecture begins with extension methods as a way to simplify code by extending existing types. It then moves into comprehensive file and directory operations including reading, writing, appending, deleting, renaming files and directories using File, Directory, StreamWriter, FileInfo, and DirectoryInfo classes. The final major topic is reflection, covering how to get type information, enumerate type members, invoke methods dynamically, and build a complete profile save/load system using reflection.
📝 Lecture Summary
Extension Methods
Extension methods allow you to add new methods to existing types without modifying them. They are defined as static methods in a static class, with the first parameter containing the this keyword to specify which type the method extends. This provides a cleaner syntax for utility operations.
🔑 Definition — Extension Method: A static method that appears to be an instance method of a type, defined in a static class with the first parameter prefixed by this.
📌 Example: Instead of calling MyUtils.IsNumeric("4"), you can create an extension method so you can call "4".IsNumeric(). The extension method is defined as public static bool IsNumeric(this string s) that internally uses float.TryParse(s, out output).
File Handling Basics
The System.IO namespace provides classes for file operations. File.Exists() checks if a file exists. File.ReadAllText() reads entire file content into a string. File.WriteAllText() overwrites file content. File.AppendAllText() adds content without overwriting.
📌 Example: Check if "test.txt" exists, read and display its current content, then prompt user for new content and write it:
if(File.Exists("test.txt"))
{
string content = File.ReadAllText("test.txt");
Console.WriteLine("Current content of file:");
Console.WriteLine(content);
}
Console.WriteLine("Please enter new content for the file:");
string newContent = Console.ReadLine();
File.WriteAllText("test.txt", newContent);
📌 Example: Continuous editing with append until user types "exit":
Console.WriteLine("Please enter new content - type exit and press enter to finish editing:");
string newContent = Console.ReadLine();
while (newContent != "exit")
{
File.AppendAllText("test.txt", newContent + Environment.NewLine);
newContent = Console.ReadLine();
}
Using Statement and StreamWriter
The using statement ensures proper disposal of unmanaged resources like file streams. When you create a StreamWriter inside a using block, its Dispose() method is automatically called at the end, which closes the file and releases system resources.
📌 Example: Using StreamWriter with using statement:
using (StreamWriter sw = new StreamWriter("test.txt"))
{
string newContent = Console.ReadLine();
while (newContent != "exit")
{
sw.Write(newContent + Environment.NewLine);
newContent = Console.ReadLine();
}
}
💡 Why this matters: Without using, you would need to manually call sw.Close() to ensure the file is properly closed, which can lead to resource leaks if forgotten.
File and Directory Operations
- Delete a file:
File.Delete("test.txt") - Delete a directory:
Directory.Delete("testdir") - Rename a file: Use
File.Move("oldname.txt", "newname.txt") - Rename a directory: Use
Directory.Move("olddir", "newdir") - Create a directory:
Directory.CreateDirectory("newdirname")
📌 Example: Rename a file with user input:
if (File.Exists("test.txt"))
{
Console.WriteLine("Please enter a new name for this file:");
string newFileName = Console.ReadLine();
if (newFileName != String.Empty)
{
File.Move("test.txt", newFileName);
if (File.Exists(newFileName))
{
Console.WriteLine("The file was renamed to " + newFileName);
}
}
}
FileInfo and DirectoryInfo
FileInfo provides detailed information about a specific file, such as name, size, and last modified date. DirectoryInfo gives information about a directory and can enumerate its files and subdirectories.
🔑 Definition — FileInfo: An object that provides properties and instance methods for creating, copying, deleting, moving, and opening files.
📌 Example: Get information about the executing assembly's file:
FileInfo fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (fi != null)
Console.WriteLine(String.Format("Information about file: {0}, {1} bytes", fi.Name, fi.Length));
📌 Example: List all files in a directory using DirectoryInfo:
DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
if (di != null)
{
FileInfo[] subFiles = di.GetFiles();
Console.WriteLine("Files:");
foreach (FileInfo subFile in subFiles)
{
Console.WriteLine(" " + subFile.Name + " (" + subFile.Length + " bytes)");
}
}
📌 Example: List subdirectories:
DirectoryInfo[] subDirs = di.GetDirectories();
Console.WriteLine("Directories:");
foreach (DirectoryInfo subDir in subDirs)
{
Console.WriteLine(" " + subDir.Name);
}
Introduction to Reflection
Reflection allows you to examine and interact with type metadata at runtime. Using System.Reflection, you can get type information, enumerate properties, methods, constructors, and even invoke them dynamically.
🔑 Definition — Reflection: The ability to obtain information about types, their members, and to invoke members at runtime, even when the type is not known at compile time.
📌 Example: Get type names at runtime:
string test = "test";
Console.WriteLine(test.GetType().FullName); // System.String
Console.WriteLine(typeof(Int32).FullName); // System.Int32
📌 Example: List all types in the current assembly:
Assembly assembly = Assembly.GetExecutingAssembly();
Type[] assemblyTypes = assembly.GetTypes();
foreach(Type t in assemblyTypes)
Console.WriteLine(t.Name);
Invoking Methods with Reflection
Using reflection, you can create instances of types and invoke their methods dynamically, even when the type is not known at compile time.
📌 Example: Create instance and invoke method via reflection:
Type testType = typeof(TestClass);
ConstructorInfo ctor = testType.GetConstructor(System.Type.EmptyTypes);
if (ctor != null)
{
object instance = ctor.Invoke(null);
MethodInfo methodInfo = testType.GetMethod("TestMethod");
Console.WriteLine(methodInfo.Invoke(instance, new object[] { 10 }));
}
Complete Profile Save/Load System with Reflection
This comprehensive example demonstrates how to build a persistent settings system that saves and loads object properties automatically using reflection, without knowing the property names at compile time.
🔑 Definition — PropertyInfo: A reflection object that provides access to a property of a type, including getting and setting its value.
📌 Key elements of the Person class:
Load()method: Reads "settings.dat", parses each line as "PropertyName|Value", finds the property usingType.GetProperty(), and sets its value using the customSetProperty()methodSave()method: Gets all properties usingType.GetProperties(), writes each as "Name|Value" to "settings.dat"SetProperty()method: Handles type conversion (Int32, String) and callsPropertyInfo.SetValue()with appropriate conversion
📌 Example: Using the profile system:
Person person = new Person();
person.Load();
if ((person.Age > 0) && (person.Name != String.Empty))
{
Console.WriteLine("Hi " + person.Name + " - you are " + person.Age + " years old!");
}
else
{
Console.WriteLine("I don't seem to know much about you. Please enter the following information:");
Type type = typeof(Person);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo propertyInfo in properties)
{
Console.WriteLine(propertyInfo.Name + ":");
person.SetProperty(propertyInfo, Console.ReadLine());
}
person.Save();
Console.WriteLine("Thank you! I have saved your information for next time.");
}
💡 Why this matters: This pattern allows creating generic save/load systems that work with any object type without writing specific code for each property.
⭐ Key Takeaways
Extension methods provide syntactic sugar for adding functionality to existing types without inheritance. File I/O operations in System.IO include reading, writing, appending, deleting, renaming files and directories, with using statement being essential for proper resource management. Reflection is a powerful meta-programming technique that enables runtime type inspection, dynamic method invocation, and building flexible systems like automatic property persistence. The profile save/load example demonstrates how to combine reflection with file I/O to create reusable object serialization without explicit property mapping. Understanding when and how to use these techniques distinguishes basic programming from advanced, maintainable application development.
🧠 Quick Revision Questions
- How do you define an extension method in C#? What special keyword and parameter arrangement is required?
- What is the difference between
File.WriteAllText()andFile.AppendAllText()? When would you use each? - Why is the
usingstatement important when working with file streams likeStreamWriter? - How can you use
FileInfoto get the size of a file andDirectoryInfoto list all files in a directory? - In the reflection-based profile save/load system, how does the
SetProperty()method handle different data types like integers versus strings?
📘 Lecture 12 — Windows Presentation Foundation (WPF)
📖 Overview: This lecture introduces Windows Presentation Foundation (WPF), a modern UI framework for building rich, visually polished Windows applications. It covers WPF's history, key features, comparison with earlier technologies, the role of XAML, and the evolution of WPF versions. Understanding WPF is essential for creating modern, designer-friendly, and hardware-accelerated user interfaces.
🗂️ Topics Covered
This lecture begins with the history and evolution of WPF from its 2003 codename "Avalon" to WPF 4.0 in 2010. It then explores the highlights of WPF including broad integration, resolution independence, hardware acceleration, declarative programming with XAML, and rich composition. The lecture compares WPF with earlier UI technologies like Win32, GDI, GDI+, Windows Forms, and DirectX. It discusses new features in WPF 3.5/3.5SP1 and WPF 4.0, contrasts WPF with Silverlight, and introduces XAML as a declarative markup language for describing interfaces.
📝 Lecture Summary
Chapter 12 — Lecture 12: Windows Presentation Foundation (WPF)
WPF (Windows Presentation Foundation) was publicly announced in 2003 under the codename "Avalon." WPF 4.0 was released in April 2010. It has a steep learning curve because code must be written in many places, and there are multiple ways to perform a particular task. WPF enables polished user interfaces that receive significant attention, allows rapid iterations and major interface changes throughout development, and keeps user interface description separate from implementation. Developers can create an "ugly" application that designers can re-theme, which is difficult in the Win32 style of programming where code to re-paint the user interface is mixed with program logic.
Earlier UI technologies include GDI (Graphics Device Interface), introduced in Windows 1.0 in 1985; OpenGL, a leap ahead introduced in the 90s; DirectX, introduced in 1995 with DirectX 2 in 1996; GDI+, a newer library based on DirectX also used behind Xbox graphics; Windows Forms, the primary way of UI design in C#; and XNA, which provides a managed library for DirectX and is great for game development (no .NET/COM interoperability required). A simple example is drawing bitmaps on buttons, which can be efficiently done using GDI.
The highlights of WPF are: 1) Broad integration — includes 2D, 3D, video, speech libraries, etc. 2) Resolution independence — WPF emphasizes vector graphics. 3) Hardware acceleration — based on Direct3D, but can work using a software pipeline if Direct3D hardware is not available. 4) Declarative programming — using Extensible Application Markup Language (XAML, pronounced "Zammel"). Custom attribute and configuration files have always existed, but XAML is very rich. 5) Rich composition and customization — for example, you can create a combobox filled with animated buttons or a menu filled with live video clips, and it is quite easy to skin applications.
💡 Why this matters: WPF's rich composition allows developers to embed interactive elements (like animated buttons or live video) directly into standard UI controls, enabling highly dynamic and visually engaging applications that were previously difficult to build.
In short, WPF aims to combine the best attributes of systems such as DirectX (3D and hardware acceleration), Windows Forms (developer productivity), Adobe Flash (powerful animation support), and HTML (declarative markup). The first release in November 2006 was WPF 3.0 because it shipped as part of the .NET Framework 3.0. WPF 3.5 came a year later. The next version as part of .NET 3.5 SP1 came in August 2008. The WPF Toolkit, released in August 2008, was experimental and has quick releases. Regarding tool support, WPF extensions for Visual Studio 2005 arrived a few months after the first WPF release, along with a public release of Expression Blend. Now, Visual Studio 2012 is a first-class WPF development environment, mostly rewritten using WPF, and Expression Blend is 100% WPF and great for designing and prototyping WPF apps.
New features in WPF 3.5/3.5SP1 include: Interactive3D with 2D elements in 3D scenes; first-class interoperability with DirectX; better data binding using XLINQ; better validation and debugging (reducing code); better special effects; high-performance custom drawing; text improvements; enhancements to partial-trust apps; improved deployment; and improved performance.
New features in WPF 4.0 include: multi-touch support — compatible with Surface API v2; Windows 7 support like jump lists and new common dialogs; new controls like DataGrid and Calendar; easing animation functions (bounce, elastic); enhanced styling with Visual State Manager; improved layout on pixel boundaries; non-blurry text (with some limitations, so must opt-in); deployment improvements; and performance improvements.
Silverlight vs WPF
Silverlight is a lightweight version of WPF for the web. It chose to follow the WPF approach. First released in 2007, version 4 was released in April 2010, near WPF 4. There is often confusion about when to use one or the other. Both can run in and outside the web. Silverlight is mostly a subset of WPF, but there are some incompatibilities. Decisions include: should I use full .NET or partial .NET, and should I have the ability to run on other devices (e.g., Macs)? Ideally, a common codebase should work for both, but currently the best approach is using #ifdefs to handle incompatibilities.
🔑 Definition — Silverlight: A lightweight version of WPF designed for web applications, mostly a subset of WPF with some incompatibilities. It can run on other platforms like Macs.
📌 Example: If you want to run your application on both Windows and Mac, you might use Silverlight. To handle differences between WPF and Silverlight code, you can use #ifdef SILVERLIGHT in your C# code to conditionally compile platform-specific sections.
XAML — Extensible Application Markup Language
XAML is primarily used to describe interfaces in WPF and Silverlight. It is also used to express activities and configurations in Workflow Foundation (WF) and Windows Communication Foundation (WCF). XAML is a common language for programmers and other experts, such as UI design experts. Field-specific development tools can be created. Field experts are graphic designers, who can use a design tool such as Expression Blend. Beyond coordinating with designers, XAML is good for a concise way to represent UI or hierarchies of objects, encourages separation of front-end and back-end, provides tool support with copy and paste, and is used by all WPF tools.
🔑 Definition — XAML: An XML-based declarative programming language for creating and initializing objects. It is a way to use .NET APIs. It has few keywords and elements, and it does not make sense without .NET, just as C# does not make sense without .NET. Microsoft formalized XAML vocabularies, such as the WPF XAML vocabulary.
📌 Example: Instead of writing C# code to create a button, you can write in XAML:
<Button Content="Click Me" Width="100" Height="30"/>
This declaratively creates and initializes a Button object with specified properties.
Comparisons with SVG and HTML are misguided. XAML is XML with a set of rules. Online specifications for the XAML object specification language and WPF and Silverlight vocabularies are available. XAML is used by other technologies as well, although it was originally designed for WPF. Using XAML in WPF projects is optional — everything can be done in procedural code as well, although it is rare to find it done that way.
⭐ Key Takeaways
WPF is a modern UI framework that integrates 2D, 3D, video, and speech, emphasizes vector graphics for resolution independence, and uses hardware acceleration via Direct3D. Its declarative programming model using XAML separates UI design from code, enabling designers and developers to work collaboratively. Key versions include WPF 3.0 (2006), 3.5 (2007), and 4.0 (2010) with multi-touch and Windows 7 support. Silverlight is a lightweight subset for web, mostly compatible but requiring #ifdef for platform differences. XAML is an XML-based declarative language essential for describing WPF interfaces but is optional — all tasks can be done in procedural code.
🧠 Quick Revision Questions
- What are the five main highlights of WPF, and explain each briefly?
- How does WPF differ from Win32 and Windows Forms in terms of UI redesign and developer/designer collaboration?
- What new features were introduced in WPF 4.0, and how do they improve the developer experience?
- When should you choose Silverlight over WPF, and what technique is used to handle code incompatibilities between them?
- Why is XAML described as a "declarative programming language," and is it mandatory to use it in WPF projects? Explain.
📘 Lecture 13 — XAML Specification and Mapping Rules
📖 Overview: This lecture explores how XAML defines rules that map .NET namespaces, types, properties, and events into XML namespaces, elements, and attributes. It covers the fundamental relationships between XAML declarations and equivalent C# code, making it essential for understanding how WPF applications are constructed declaratively.
🗂️ Topics Covered
The lecture covers XAML object elements and their mapping to .NET objects, property and event attributes, XML namespace declarations including the XAML language namespace, property elements for rich composition, type converters for automatic string-to-object conversion, markup extensions with curly brace syntax, content properties, and collection handling including lists and dictionaries.
📝 Lecture Summary
XAML Object Elements and Property/Event Attributes
XAML specification defines rules that map .NET namespaces, types, properties, and events into XML namespaces, elements, and attributes. Declaring an XML element in XAML (known as an object element) is equivalent to instantiating the corresponding .NET object via a default constructor. Setting an attribute on the object element is equivalent to setting a property of the same name (called a property attribute) or hooking up an event handler of the same name (called an event attribute).
🔑 Definition — Object Element: An XML element in XAML that represents a .NET object instantiated via its default constructor.
📐 Formula:
XAML: <Button xmlns="..." Content="OK"/> → C#: System.Windows.Controls.Button b = new System.Windows.Controls.Button(); b.Content = "OK";
📌 Example:
XAML: <Button xmlns="..." Content="OK" Click="button_Click"/>
Equivalent C#:
System.Windows.Controls.Button b = new System.Windows.Controls.Button();
b.Click += new System.Windows.RoutedEventHandler(button_Click);
b.Content = "OK";
💡 Why this matters: XAML can no longer run standalone in the browser because event handlers are attached before properties are set, requiring code-behind.
Namespaces in XAML
Mapping to the namespace used above and other WPF namespaces is hard-coded inside the WPF assemblies. The root object element in XAML must specify at least one XML namespace that qualifies itself and any child elements. Additional XML namespaces (on the root or on children) must be given a distinct prefix.
📌 Example:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" — This is the XAML language namespace, which maps to types in the System.Windows.Markup namespace but also defines special directives for the XAML compiler or parser.
All of the following are mapped with http://schemas.microsoft.com/winfx/2006/xaml/presentation:
System.Windows, System.Windows.Automation, System.Windows.Controls, System.Windows.Controls.Primitives, System.Windows.Data, System.Windows.Documents, System.Windows.Forms.Integration, System.Windows.Ink, System.Windows.Input, System.Windows.Media, System.Windows.Media.Animation, System.Windows.Media.Effects, System.Windows.Media.Imaging, System.Windows.Media.Media3D, System.Windows.Media.TextFormatting, System.Windows.Navigation, System.Windows.Shapes, System.Windows.Shell
Property Elements
Property elements in XAML enable rich composition. The period distinguishes property elements from object elements. Property elements don't have attributes except x:uid for localization.
📌 Example — Setting a Rectangle as Button content: C#:
System.Windows.Controls.Button b = new System.Windows.Controls.Button();
System.Windows.Shapes.Rectangle r = new System.Windows.Shapes.Rectangle();
r.Width = 40;
r.Height = 40;
r.Fill = System.Windows.Media.Brushes.Black;
b.Content = r;
XAML equivalent:
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Button.Content>
<Rectangle Height="40" Width="40" Fill="Black"/>
</Button.Content>
</Button>
📌 Example — Simple properties with property element syntax:
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Button.Content>OK</Button.Content>
<Button.Background>White</Button.Background>
</Button>
This is equivalent to setting Content="OK" and Background="White" as attributes.
Type Converters
WPF provides type converters for many common data types: Brush, Color, FontWeight, Point, and so on. Classes deriving from TypeConverter (BrushConverter, ColorConverter) convert from string to the corresponding types. You can also write your own type converters for custom data types.
🔑 Definition — Type Converter: A class that converts string values to .NET objects at runtime, enabling XAML attributes to use simple string representations.
📌 Example — Setting Background with type converter:
<Button xmlns="..." Content="OK">
<Button.Background>
<SolidColorBrush Color="White"/>
</Button.Background>
</Button>
📌 Example — Without the type converter for Color:
<Button xmlns="..." Content="OK">
<Button.Background>
<SolidColorBrush>
<SolidColorBrush.Color>
<Color A="255" R="255" G="255" B="255"/>
</SolidColorBrush.Color>
</SolidColorBrush>
</Button.Background>
</Button>
Even that requires a type converter for Byte.
C# equivalent without type converter:
B.Background = (Brush)System.ComponentModel.TypeDescriptor.GetConverter(
typeof(Brush)).ConvertFromInvariantString("White");
💡 Why this matters: Constants in strings cause problems with runtime exceptions not caught at compile time, although Visual Studio checks XAML at compile time.
Markup Extensions
Markup extensions, like type converters, enable extending the expressiveness of XAML. Both can evaluate a string attribute value at runtime and produce an appropriate object. Unlike type converters, markup extensions are invoked from XAML with explicit and consistent syntax, making them a preferred approach.
Whenever an attribute value is enclosed in curly braces {}, the XAML compiler/parser treats it as a markup extension value rather than a literal string.
🔑 Definition — Markup Extension: A class with a default constructor used in XAML with curly brace syntax to produce objects at runtime.
📌 Example:
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="{x:Null}"
Height="{x:Static SystemParameters.IconHeight}"
Content="{Binding Path=Height, RelativeSource={RelativeSource Mode=Self}}"/>
This works because positional parameters have corresponding property values, and the markup extension has the real code to be executed.
📌 Example — Escaping curly braces for literal text:
<Button xmlns=''http://schemas.microsoft.com/winfx/2006/xaml/presentation''
Content=''{}{This is not a markup extension!}''/>
Content Properties and Collection Items
An object element can have three types of children: a value for a content property, collection items, or a value that can be type-converted to the object element. The designated property is the content property.
🔑 Definition — Content Property: The default property of a class that receives child content when no explicit property element is specified.
📌 Example — Content property shorthand:
<!-- Full syntax -->
<Button xmlns="..." Content="OK"/>
<!-- Equivalent using content property -->
<Button xmlns="...">OK</Button>
📌 Example — Content property with complex object:
<!-- Full syntax -->
<Button xmlns="...">
<Button.Content>
<Rectangle Height="40" Width="40" Fill="Black"/>
</Button.Content>
</Button>
<!-- Equivalent using content property -->
<Button xmlns="...">
<Rectangle Height="40" Width="40" Fill="Black"/>
</Button>
There is no requirement that the content property must actually be called "Content"; classes such as ComboBox, ListBox, and TabControl use their Items property as the content property, designated with a custom attribute.
Collections: Lists and Dictionaries
XAML enables adding items to the two main types of collections that support indexing: lists and dictionaries.
📌 Example — ListBox with explicit collection syntax:
<ListBox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ListBox.Items>
<ListBoxItem Content="Item 1"/>
<ListBoxItem Content="Item 2"/>
</ListBox.Items>
</ListBox>
Equivalent C#:
System.Windows.Controls.ListBox listBox = new System.Windows.Controls.ListBox();
System.Windows.Controls.ListBoxItem item1 = new System.Windows.Controls.ListBoxItem();
System.Windows.Controls.ListBoxItem item2 = new System.Windows.Controls.ListBoxItem();
item1.Content = "Item 1";
item2.Content = "Item 2";
📌 Example — ListBox with content property shorthand:
<ListBox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ListBoxItem Content="Item 1"/>
<ListBoxItem Content="Item 2"/>
</ListBox>
📌 Example — Using a dictionary (ResourceDictionary):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="1" A="255" R="255" G="255" B="255"/>
<Color x:Key="2" A="0" R="0" G="0" B="0"/>
</ResourceDictionary>
Equivalent C#:
System.Windows.ResourceDictionary d = new System.Windows.ResourceDictionary();
System.Windows.Media.Color color1 = new System.Windows.Media.Color();
System.Windows.Media.Color color2 = new System.Windows.Media.Color();
color1.A = 255; color1.R = 255; color1.G = 255; color1.B = 255;
color2.A = 0; color2.R = 0; color2.G = 0; color2.B = 0;
d.Add("1", color1);
d.Add("2", color2);
⭐ Key Takeaways
XAML maps .NET objects to XML elements where each object element instantiates a .NET class, and attributes correspond to either properties or events. Two XML namespaces are essential: the WPF namespace (http://schemas.microsoft.com/winfx/2006/xaml/presentation) for all WPF controls and the XAML language namespace (xmlns:x) for directives like x:Key and x:Null. Type converters automatically convert string attribute values to their appropriate .NET types (like "White" to a Brush), while markup extensions use curly brace syntax {} for more complex runtime object creation. Property element syntax (e.g., <Button.Content>) enables rich composition when simple attribute syntax is insufficient, and collection content properties (like Items for ListBox) allow direct child element placement without explicit property elements.
🧠 Quick Revision Questions
- What is the equivalent C# code for the XAML declaration
<Button xmlns="..." Content="OK" Click="button_Click"/>? - What are the two XML namespaces typically used in WPF XAML files, and what do they map to?
- How do type converters enable setting properties like
Background="White"in XAML? - What is the syntax difference between a markup extension and a type converter in XAML?
- How does the content property work, and give an example of a class that uses a property other than "Content" as its content property?
📘 Lecture 14 — XAML and Procedural Code Integration
📖 Overview: This lecture explores advanced XAML techniques including working with non-XAML designed classes like Hashtable, understanding XAML child element processing rules, and integrating XAML with procedural code. It demonstrates how to load XAML at runtime, name elements for easy access, and compile XAML with code-behind files for building complete WPF applications.
🗂️ Topics Covered
The lecture covers representing non-XAML classes like Hashtable in XAML, the detailed rules for processing XAML child elements (IList, IDictionary, content property, type converter), mixing XAML with procedural code using XamlReader, accessing elements by name using FindName and x:Name, using property element syntax for named references, Binding markup extensions and x:Reference, and the three-step XAML compilation process with code-behind files.
📝 Lecture Summary
Type Converters and Abstract Classes
A type converter exists that converts string to SolidColorBrush, allowing markup like <SolidColorBrush>White</SolidColorBrush>. This works even though no designated content property exists. Similarly, <Brush>White</Brush> works even though Brush is abstract, because the type converter can convert a string to a SolidColorBrush.
🔑 Definition — Type Converter: A mechanism that converts a string value to an object of a specific type during XAML parsing.
Representing Non-XAML Classes (Hashtable Example)
Classes not originally designed for XAML, like System.Collections.Hashtable, can still be represented. A Hashtable with entries can be written as:
<Collections:Hashtable
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<sys:Int32 x:Key="key1">7</sys:Int32>
<sys:Int32 x:Key="key2">23</sys:Int32>
</Collections:Hashtable>
💡 Why this matters: This demonstrates how XAML can work with any .NET class, not just those specifically designed for XAML.
XAML Child Element Rules
The rules for processing child elements in XAML are evaluated in order:
- If the type implements IList: Call
IList.Addfor each child element. - If the type implements IDictionary: Call
IDictionary.Addfor each child, using thex:Keyattribute value for the key and the element for the value. (XAML2009 checks IDictionary before IList.) - If the parent supports a content property (indicated by
System.Windows.Markup.ContentPropertyAttribute) and the child's type is compatible: Treat the child as the value of that property. - If the child is plain text and a type converter exists to transform the child into the parent type (and no properties are set on the parent): Treat the child as input to the type converter and use the output as the parent object instance.
- Otherwise: Treat it as unknown content and potentially raise an error.
📐 Rule: XAML processes children in this order: IList → IDictionary → Content Property → Type Converter → Error
Mixing XAML and Procedural Code with XamlReader
The XamlReader class allows loading XAML at runtime:
Window window = null;
using (FileStream fs = new FileStream("mywindow.xaml", FileMode.Open, FileAccess.Read))
{
window = (Window)XamlReader.Load(fs);
}
Accessing Elements by Name
Elements can be accessed by walking children with hard-coded knowledge:
StackPanel panel = (StackPanel)window.Content;
Button okButton = (Button)panel.Children[4];
A better approach uses named elements. The x:Name attribute assigns a name:
<Button x:Name="okButton">OK</Button>
Then retrieve it using FindName:
Button okButton = (Button)window.FindName("okButton");
Classes that already have a Name property can designate it as the special name using a custom attribute.
🔑 Definition — x:Name: A XAML directive attribute that gives an element a name, enabling access from procedural code.
Named Properties and Markup Extensions
Using the Binding markup extension for element references:
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Label Target="{Binding ElementName=box}" Content="Enter _text:"/>
<TextBox Name="box"/>
</StackPanel>
This gives focus to the TextBox when the Label's access key is pressed.
WPF 4 includes a simpler x:Reference markup extension that works at parse time:
<StackPanel ...>
<Label Target="{x:Reference box}" Content="Enter _text:"/>
<TextBox Name="box"/>
</StackPanel>
If a property is marked with the System.Windows.Markup.NameReferenceConverter type converter, a simpler syntax works:
<StackPanel ...>
<Label Target="box" Content="Enter _text:"/>
<TextBox Name="box"/>
</StackPanel>
Compiling XAML
Compiling XAML involves three steps:
- Converting a XAML file into a special binary format (BAML)
- Embedding the converted content as a binary resource in the assembly being built
- Performing plumbing that connects XAML with procedural code automatically
The first step involves specifying a subclass for the root element using the x:Class keyword:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyNamespace.MyWindow">
</Window>
This is paired with a code-behind file:
namespace MyNamespace
{
partial class MyWindow : Window
{
public MyWindow()
{
// Necessary to call in order to load XAML-defined content!
InitializeComponent();
}
// Any other members can go here...
}
}
The partial keyword is important for combining the XAML-generated class with the code-behind class. Event handlers are typically defined in the code-behind file.
🔑 Definition — Code-Behind: A partial class file that contains procedural code (event handlers, business logic) paired with a XAML file, together forming a complete class.
⭐ Key Takeaways
XAML can represent non-XAML designed classes like Hashtable using appropriate namespace declarations and the x:Key attribute for dictionary entries. The child element processing rules follow a strict priority: IList first, then IDictionary, then content property, then type converter, and finally error. Elements can be named with x:Name and retrieved from procedural code using FindName, eliminating the need for fragile hard-coded child indices. XAML compilation converts markup to BAML binary format, embeds it as a resource, and automatically connects it with code-behind partial classes using x:Class. The Binding markup extension and the newer x:Reference markup extension both enable element references, but x:Reference works at parse time and is simpler.
🧠 Quick Revision Questions
- What is the order of priority for XAML child element processing rules?
- How would you represent a Hashtable with three string-integer pairs in XAML?
- What are the three steps involved in compiling XAML?
- How do you access a named button, declared with
x:Name="submitButton", from procedural code? - Why is the
partialkeyword important in a code-behind file?
📘 Lecture 15 — BAML, XAML Features, and WPF Fundamentals
📖 Overview: This lecture covers BAML (Binary Application Markup Language) as a compressed representation of XAML, explores XAML 2009 and 2006 keywords and markup extensions, and introduces the fundamental WPF class hierarchy including DependencyObject, Visual, UIElement, and FrameworkElement. It also explains the difference between logical and visual trees and how to traverse them programmatically.
🗂️ Topics Covered
The lecture begins with BAML and its relationship to XAML, including loading mechanisms and conversion back to XAML. It then discusses XAML 2009 features and XAML 2006 keywords, followed by markup extensions. The second half introduces WPF fundamentals through its deep inheritance hierarchy, covering key classes from DispatcherObject to Control. Finally, the lecture explains logical versus visual trees and provides code examples for traversing both trees using helper classes.
📝 Lecture Summary
BAML and XAML Loading
BAML (Binary Application Markup Language) is a compressed representation of XAML. There is a BAML reader available. Earlier there was CAML (Compiled Application Markup Language) but it is no longer used. When using x:Class, some glue code is generated, similar to loading and parsing the XAML file. You must call InitializeComponent() and can refer to named elements like class members.
Procedural code can be written inside the XAML file using <x:Code> with CDATA sections. However, you must avoid ]] inside the code; if needed, use entities like < and &. There is no good reason to embed code in XAML, as internally the build system compiles it into a .cs file.
BAML can be converted back to XAML:
System.Uri uri = new System.Uri("/wpfapplication1;component/mywindow.xaml", System.urikind.Relative);
Window window = (Window)Application.LoadComponent(uri);
String xaml = XamlWriter.Save(window);
There are different loading mechanisms: from a resource identified by the original XAML file, or from the integrated BAML that is loaded.
🔑 Definition — BAML: Binary Application Markup Language — a compressed binary representation of XAML that improves load performance.
XAML 2009 and 2006 Features
Key features of XAML 2009 include: full generics support using typearguments, dictionary keys of any type, built-in system data types, creating objects with non-default constructors, getting instances via factory methods, event handlers can be markup extensions returning delegates, and defining additional members and properties. The System.Xaml.XamlXmlReader class can be extended with readers and writers for many formats. It abstracts differences in XAML formats like accessing a content property, property element, or property attribute.
XAML 2006 keywords include:
x:AsyncRecords— controls the size of asynchronous XAML-loading chunksx:Class— specifies the code-behind classx:ClassModifier— visibility (public by default)x:Code— inline codex:ConnectionId— not for public usex:FieldModifier— field visibility (internal by default)x:Key— resource dictionary keyx:Name— element namex:Shared—=falsemeans same resource instance not sharedx:Subclass— only needed when partial classes not supportedx:SynchronousMode— XAML loaded in async modex:TypeArguments— used only with root withx:Classin XAML 2006x:Uid— representsSystem.Urix:XData— data opaque for XAML parser
Markup extensions often confused with keywords include:
x:Array— use withx:Typeto define type of arrayx:Null— null valuex:Reference— reference to named elementx:Static— static property/fieldx:Type— like thetypeofoperator
💡 Why this matters: Understanding these keywords and markup extensions is essential for proper XAML authoring and for reading/writing XAML programmatically.
WPF Class Hierarchy
WPF concepts are above and beyond .NET concepts, causing the steep learning curve of WPF. It has a deep inheritance hierarchy with a handful of fundamental classes.
Key classes in the hierarchy (from base to derived):
🔑 Definition — DispatcherObject: The base class for any object that wishes to be accessed only on the thread that created it. Most WPF classes derive from DispatcherObject and are inherently thread-unsafe.
🔑 Definition — DependencyObject: The base class for any object that can support dependency properties.
🔑 Definition — Freezable: The base class for objects that can be "frozen" into a read-only state for performance reasons. Freezables, once frozen, can be safely shared among multiple threads, unlike DispatcherObjects. Frozen objects can never be unfrozen, but you can clone them.
🔑 Definition — Visual: The base class for all objects that have their own 2D visual representation.
🔑 Definition — UIElement: The base class for all 2D visual objects with support for routed events, command binding, layout, and focus.
🔑 Definition — Visual3D: The base class for all objects that have their own 3D visual representation.
🔑 Definition — UIElement3D: The base class for all 3D visual objects with support for routed events, command binding, and focus.
🔑 Definition — ContentElement: A base class similar to UIElement but for document-related pieces of content that don't have rendering behavior on their own. Instead, ContentElements are hosted in a Visual-derived class to be rendered on screen.
🔑 Definition — FrameworkElement: The base class that adds support for styles, data binding, resources, tooltips, and context menus.
🔑 Definition — FrameworkContentElement: The analog to FrameworkElement for content.
🔑 Definition — Control: The base class for familiar controls such as Button, ListBox, and StatusBar. Control adds properties like Foreground, Background, and FontSize, as well as the ability to be completely restyled.
Logical and Visual Trees
XAML is good for UI because of its hierarchical nature. A logical tree exists even without XAML. Properties, events, and resources are tied to logical trees. Properties are propagated down and events can be routed up or down the tree. It is a simplification of what actually happens during rendering.
The visual tree can be thought of as an extension of the logical tree, though some things can be dropped as well. The visual tree exposes visual implementation details — for example, a ListBox is actually a Border, two ScrollBars, and more. Only things from Visual or Visual3D appear in a visual tree. You should avoid depending on the visual tree in your code.
You can traverse trees using System.Windows.LogicalTreeHelper and System.Windows.Media.VisualTreeHelper.
The visual tree is empty until the dialog box is rendered. Navigating either tree can be done in instance methods of the elements themselves — for example, the Visual class has protected members VisualParent, VisualChildrenCount, and GetVisualChild. FrameworkElement and FrameworkContentElement have a Parent property and a LogicalChildren property.
📌 Example: A code-behind file that prints both logical and visual trees:
void PrintLogicalTree(int depth, object obj)
{
Debug.WriteLine(new string(' ', depth) + obj);
if (!(obj is DependencyObject))
return;
foreach (object child in LogicalTreeHelper.GetChildren(obj as DependencyObject))
PrintLogicalTree(depth + 1, child);
}
void PrintVisualTree(int depth, DependencyObject obj)
{
Debug.WriteLine(new string(' ', depth) + obj);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
}
This code recursively traverses the logical tree using LogicalTreeHelper.GetChildren and the visual tree using VisualTreeHelper.GetChildrenCount and GetChild.
⭐ Key Takeaways
- BAML is a compressed binary form of XAML that improves performance; it can be converted back to XAML using
Application.LoadComponentandXamlWriter.Save. - XAML provides both keywords (like
x:Class,x:Name,x:Key) and markup extensions (likex:Static,x:Type,x:Null) that serve different purposes and are essential for WPF development. - The WPF class hierarchy is deep and fundamental — starting from Object → DispatcherObject → DependencyObject → Freezable → Visual → UIElement → FrameworkElement → Control — understanding this hierarchy explains thread safety, dependency properties, freezing, and visual rendering behavior.
- Logical trees represent the simplified UI structure for property/event propagation, while visual trees expose the actual rendering details; use
LogicalTreeHelperandVisualTreeHelperfor traversal, but avoid depending on visual tree structure in code. - The visual tree is only populated after rendering occurs, and most WPF objects are thread-unsafe due to their DispatcherObject inheritance.
🧠 Quick Revision Questions
- What is the difference between BAML and XAML?
- List three XAML 2009 features that are not available in XAML 2006.
- What is the difference between
x:Nameandx:Keyin XAML? - Why are most WPF objects inherently thread-unsafe, and which class allows safe multi-threaded sharing when frozen?
- How does the logical tree differ from the visual tree, and which helper classes are used to traverse each?
📘 Lecture 16 — Dependency Properties and Attached Properties
📖 Overview: This lecture introduces dependency properties, a fundamental WPF infrastructure that extends standard .NET properties with features like change notification, property value inheritance, and support for multiple value providers. Understanding dependency properties is essential for working with WPF because only dependency properties can be styled and animated.
🗂️ Topics Covered
This lecture covers the motivation and implementation of dependency properties, the standard pattern for registering them with static fields and property wrappers, their benefits including per-instance memory savings and change notification, property triggers in XAML, property value inheritance across element trees, the five-step process for determining property values with precedence rules, and attached properties as a special type of dependency property.
📝 Lecture Summary
Dependency Properties: Motivation and Implementation
Dependency properties depend on multiple providers for determining their value at any point in time. Providers can include animations, parent properties propagating down, and more. The biggest feature is change notification. Motivation is to add rich functionality from declarative markup without procedural code. For example, Button has 111 public properties (98 inherited), and setting these from XAML or a design tool would be hard without dependency properties. Key features include change notification, property value inheritance, and support for multiple providers. Only dependency properties can be styled and animated. In practice, dependency properties are normal .NET properties with extra WPF infrastructure.
🔑 Definition — Dependency Property: A property whose value depends on multiple providers (animations, parent elements, styles, etc.) and is represented by System.Windows.DependencyProperty.
📌 Example: Standard dependency property implementation for Button:
public class Button : ButtonBase
{
// The dependency property
public static readonly DependencyProperty IsDefaultProperty;
static Button()
{
// Register the property
Button.IsDefaultProperty = DependencyProperty.Register("IsDefault", typeof(bool), typeof(Button));
}
// A .NET property wrapper (optional)
public bool IsDefault
{
get { return (bool)GetValue(Button.IsDefaultProperty); }
set { SetValue(Button.IsDefaultProperty, value); }
}
// A property changed callback (optional)
private static void OnIsDefaultChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
//...
}
}
Dependency properties are represented by System.Windows.DependencyProperty. By convention, they are public static with the Property suffix. This is required by localization tools, XAML loading, etc. Optionally, metadata can be passed via overloads of Register to customize how the property is treated by WPF, and callbacks for handling property value changes, coercing values, and validating values. The .NET property wrapper is optional but helps with setting from XAML; otherwise, GetValue and SetValue methods inherited from System.Windows.DependencyObject must be used. GetValue and SetValue do not support generics because dependency properties were introduced before generic support in C#. Visual Studio has a snippet called propdp that automatically expands into a dependency property definition. .NET property wrappers are bypassed at runtime when setting dependency properties in XAML—WPF calls GetValue and SetValue directly. Therefore, property wrappers should not contain any logic beyond these calls; custom logic should use registered callbacks.
💡 Why this matters: Although the code seems verbose, dependency properties save per-instance cost by using only a static field and an efficient sparse storage system. If all Button properties were .NET properties with backing store, they would consume much more space (e.g., 111 fields for Button, but 89 are dependency properties). Additionally, code for thread access checking and re-rendering is handled automatically.
Change Notification via Property Triggers
Dependency properties support change notification based on metadata at register time. Actions can include re-rendering elements, updating layout, refreshing data bindings, and firing property triggers. For example, you can change color on hovering.
With procedural code:
void Button_MouseEnter(object sender, MouseEventArgs e)
{
Button b = sender as Button;
if (b != null) b.Foreground = Brushes.Blue;
}
void Button_MouseLeave(object sender, MouseEventArgs e)
{
Button b = sender as Button;
if (b != null) b.Foreground = Brushes.Black;
}
And with XAML triggers:
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Blue"/>
</Trigger>
Triggers cannot be applied directly to Button elements. They must be wrapped in a Style:
<Button MinWidth="75" Margin="10">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Property Value Inheritance
Property value inheritance is the flowing of property values down an element tree. For example:
<StackPanel>
<Label FontWeight="Bold" FontSize="20" Foreground="White">WPF 4 Unleashed</Label>
<Label>2010 SAMS Publishing</Label>
<Label>Installed Chapters:</Label>
<ListBox>
<ListBoxItem>Chapter 1</ListBoxItem>
<ListBoxItem>Chapter 2</ListBoxItem>
</ListBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button MinWidth="75" Margin="10">Help</Button>
<Button MinWidth="75" Margin="10">OK</Button>
</StackPanel>
<StatusBar>You have successfully registered this product.</StatusBar>
</StackPanel>
Inheritance does not affect the StatusBar because not every dependency property participates in inheritance (they must opt-in). Some controls like StatusBar internally set their values to system defaults. Property value inheritance also applies to triggers inside a definition.
Value Determination: Five-Step Process
WPF has many mechanisms that independently attempt to set the value of dependency properties. Dependency properties are designed to depend on these providers in a consistent and orderly manner. The process has five steps:
Step 1: Determine base value — Ten providers set the value with a specific precedence (highest to lowest):
- Local value (
DependencyObject.SetValue, property assignment in XAML) - Parent template trigger
- Parent template
- Style triggers
- Template triggers
- Style setters
- Theme style triggers
- Theme style setters
- Property value inheritance
- Default value (initial value registered with the property)
This explains why the StatusBar did not get font propagated. Use DependencyPropertyHelper.GetValueSource to find which source was used.
Step 2: Evaluate — Expressions (e.g., data binding) need evaluation.
Step 3: Apply animation — Animations can alter the value from Step 2 or replace it.
Step 4: Coerce — The almost-final value is passed to the CoerceValueCallback delegate if one is registered.
Step 5: Validate — The value is passed to the ValidateValueCallback delegate. Returning false causes an exception canceling the entire process. WPF 4 adds SetCurrentValue in DependencyObject, which updates the current value without changing the value source.
Attached Properties
Attached properties are special dependency properties that can be attached to arbitrary objects. They have a special XAML syntax.
Example:
<StackPanel TextElement.FontSize="30" TextElement.FontStyle="Italic" Orientation="Horizontal">
<Button MinWidth="75" Margin="10">Help</Button>
<Button MinWidth="75" Margin="10">OK</Button>
</StackPanel>
Here, TextElement.FontSize and TextElement.FontStyle are attached properties that affect child elements without those properties being defined on StackPanel itself.
⭐ Key Takeaways
The key takeaways from this lecture are that dependency properties extend standard .NET properties with WPF-specific infrastructure including change notification, property value inheritance across element trees, and a five-step value determination process with ten precedence levels. Only dependency properties can be styled and animated in WPF. The implementation pattern involves a public static readonly field, registration via DependencyProperty.Register, and an optional property wrapper that must only call GetValue/SetValue without additional logic. Property triggers enable declarative behavior changes without procedural code. Attached properties are dependency properties that can be applied to any object, enabling cross-type property setting (like TextElement.FontSize on a StackPanel). Memory efficiency is achieved because dependency properties use static fields and sparse storage instead of per-instance backing fields.
🧠 Quick Revision Questions
- What are the three key features of dependency properties that motivate their use over standard .NET properties?
- Why must .NET property wrappers for dependency properties contain no logic other than
GetValue/SetValuecalls? - List the ten providers that determine the base value of a dependency property in order of precedence.
- Why did the StatusBar in the property value inheritance example not inherit the font settings from the parent StackPanel?
- What is the difference between a dependency property and an attached property?
📘 Lecture 17 — WPF Layout, Sizing, Positioning, and Attached Properties
📖 Overview: This lecture completes the discussion of XAML and attached properties, then moves into WPF layout fundamentals. It explains how elements collaborate with parent panels to determine size and position, and covers the properties that control sizing, alignment, margins, padding, visibility, and transforms. Understanding these concepts is essential for designing responsive WPF user interfaces.
🗂️ Topics Covered
The lecture begins by revisiting a XAML window example and explaining how attached properties work internally through dependency properties and the registerattached method. It then transitions into layout, explaining the parent-child collaboration model, size-related properties (Height, Width, Min/Max, Actual sizes), Margin and Padding with their Thickness type, Visibility values, Horizontal/Vertical Alignment, Content Alignment, FlowDirection for right-to-left languages, and finally LayoutTransform vs RenderTransform.
📝 Lecture Summary
Attached Properties and Code-Behind
Attached properties allow setting properties on an element from an unrelated class. The XAML parser converts shorthand attribute values (like "Italic") into enumeration values (FontStyles.Italic) using TypeConverters. In C#, a call like TextElement.SetFontSize(panel, 30) internally calls panel.SetValue(TextElement.FontSizeProperty, 30). The property wrapper is just a convenience; it always calls DependencyObject.SetValue and GetValue.
🔑 Definition — Attached Property: A dependency property that is defined by one class but can be set on any other DependencyObject. Used to extend the functionality of existing elements without modifying them.
📌 Example: Setting a Button’s unrelated attached property from code:
okButton.SetValue(ItemsControl.IsTextSearchEnabledProperty, true);
This works because SetValue can set any dependency property on any DependencyObject.
💡 Why this matters: Attached properties allow panels (like StackPanel, Grid) to attach layout information (e.g., Grid.Row, Canvas.Top) to their children without bloating the child classes with layout-specific properties.
Registering Attached Properties
TextElement.FontSizeProperty is registered using DependencyProperty.RegisterAttached. This method is optimized for attached properties. The Control class then reuses the same property with AddOwner:
Control.FontSizeProperty = TextElement.FontSizeProperty.AddOwner(typeof(Control), ...);
This allows Control subclasses to inherit the same underlying property ID while potentially having different metadata.
📐 Formula — RegisterAttached: DependencyProperty.RegisterAttached("name", typeof(propertyType), typeof(ownerType), metadata, validateValueCallback) → Creates a new attached dependency property.
📐 Formula — AddOwner: existingProperty.AddOwner(typeof(newOwnerType), metadata) → Shares an existing property with a new class.
📌 Example: TextElement.FontSizeProperty is the canonical owner; Control.FontSizeProperty is the same underlying field, registered via AddOwner.
The Tag Property
All FrameworkElements have a Tag property of type object for storing arbitrary custom data. It is a great mechanism for extending "sealed" classes. While you can use SetValue with any property, Tag is the recommended, cleaner approach.
📌 Example:
GeometryModel3D model = new GeometryModel3D();
model.SetValue(FrameworkElement.TagProperty, "my custom data");
Layout Fundamentals
Layout in WPF boils down to parent-child relationships. Parents and children collaborate:
- Parent tells child how much space is available.
- Child asks for how much it really needs.
- Parent then positions the child.
All layout elements derive from System.Windows.UIElement. Panels (parents that support multiple children) derive from the abstract System.Windows.Controls.Panel.
Size Properties
FrameworkElement provides several size properties. Explicit Height and Width take precedence when within Min/Max range. Avoid explicit sizing when possible; defaults are Min=0, Max=Infinity. Double.NaN (or "Auto" in XAML) means "size to content".
🔑 Definition — ActualHeight/ActualWidth: Read-only properties that report the final rendered size after layout completes. 🔑 Definition — DesiredSize: The size the child requested during the measure pass of layout. 🔑 Definition — RenderSize: The final size assigned by the parent during the arrange pass.
Margin and Padding
All FrameworkElements have Margin (extra space outside the element's edges). Control (and Border) also have Padding (space inside the edges). Both are of type Thickness, which can represent 1, 2, or 4 values. Negative margins are allowed.
📐 Formula — Thickness values in XAML:
Margin="10"→ All sides = 10Margin="20,5"→ Left/Right=20, Top/Bottom=5Margin="0,10,20,30"→ Left=0, Top=10, Right=20, Bottom=30
📌 Example (C# equivalent):
myLabel.Margin = new Thickness(10);
myLabel.Margin = new Thickness(20, 5, 20, 5);
myLabel.Margin = new Thickness(0, 10, 20, 30);
The LengthConverter TypeConverter supports explicit units: cm, pt, in, px (default). All measurements are in device-independent pixels (DIPs), 1/96 inch, regardless of screen DPI.
Visibility
UIElement.Visibility can have three Visibility enum values:
🔑 Definition — Visible: Element is rendered and participates in layout (normal). 🔑 Definition — Collapsed: Element is invisible and does not participate in layout (takes no space). 🔑 Definition — Hidden: Element is invisible but still participates in layout (takes space but not drawn).
📌 Example (XAML):
<!-- Collapsed button takes no space, second button shifts up -->
<StackPanel Height="100" Background="Aqua">
<Button Visibility="Collapsed">Collapsed Button</Button>
<Button>Below a Collapsed Button</Button>
</StackPanel>
<!-- Hidden button still takes space, second button stays in place -->
<StackPanel Height="100" Background="Aqua">
<Button Visibility="Hidden">Hidden Button</Button>
<Button>Below a Hidden Button</Button>
</StackPanel>
Position and Alignment
Instead of explicit (x,y) coordinates, WPF uses alignment controlled by the parent. HorizontalAlignment values: Left, Center, Right, Stretch. VerticalAlignment values: Top, Center, Bottom, Stretch. Stretch is the default and makes the element fill the available space. Alignment is only useful when the parent gives more space than the child needs.
📌 Example (XAML):
<StackPanel>
<Button HorizontalAlignment="Left" Background="Red">Left</Button>
<Button HorizontalAlignment="Center" Background="Orange">Center</Button>
<Button HorizontalAlignment="Right" Background="Yellow">Right</Button>
<Button HorizontalAlignment="Stretch" Background="Lime">Stretch</Button>
</StackPanel>
Content Alignment
Controls also have HorizontalContentAlignment and VerticalContentAlignment. These determine how the control's content fills the space inside the control (similar relationship to Margin vs Padding). Defaults are Left and Top; Buttons override defaults. TextBlock does not stretch like a Control.
📌 Example (XAML):
<StackPanel>
<Button HorizontalContentAlignment="Left" Background="Red">Left</Button>
<Button HorizontalContentAlignment="Center" Background="Orange">Center</Button>
<Button HorizontalContentAlignment="Right" Background="Yellow">Right</Button>
<Button HorizontalContentAlignment="Stretch" Background="Lime">Stretch</Button>
</StackPanel>
FlowDirection
FrameworkElement.FlowDirection can reverse the visual flow. Used for right-to-left languages (e.g., Arabic, Hebrew).
📌 Example (XAML):
<StackPanel>
<Button FlowDirection="LeftToRight" HorizontalContentAlignment="Left" ... />
<Button FlowDirection="RightToLeft" HorizontalContentAlignment="Left" ... />
</StackPanel>
Transforms
All FrameworkElements have two Transform properties derived from System.Windows.Media.Transform:
- LayoutTransform: Applied before layout — affects the element's size and how siblings are arranged.
- RenderTransform: Applied after layout — only affects the visual appearance; siblings are not rearranged.
UIElement also has RenderTransformOrigin to specify the pivot point for the transform.
💡 Why this matters: Use LayoutTransform for permanent size changes (like rotating a button that should affect its neighbors); use RenderTransform for animations or visual effects that shouldn't disturb the layout.
⭐ Key Takeaways
- Attached properties are dependency properties registered with
RegisterAttached, allowing any element to host properties defined by unrelated classes (e.g., panels attaching layout info to children). Always prefer theTagproperty for storing custom data rather than abusing unrelated properties. - Layout is a collaborative, two-phase process: parents ask children their desired size (Measure pass), then assign them their final size and position (Arrange pass). Avoid explicit
Height/Widthwhen possible; preferMin/MaxandAuto(NaN). - Margin is space outside the element; Padding is space inside. Both use the
Thicknesstype. Visibility has three distinct states:Visible,Hidden(invisible but takes space), andCollapsed(invisible and takes no space — like removing the element). - Alignment (
HorizontalAlignment/VerticalAlignment) and ContentAlignment (HorizontalContentAlignment/VerticalContentAlignment) control placement within available space and the element's inner space respectively.Stretchis the default and makes the element fill available space. - Transforms come in two flavors:
LayoutTransformaffects layout (reflows siblings), whileRenderTransformonly affects visuals. UseRenderTransformOriginto control the pivot point.
🧠 Quick Revision Questions
- What is the difference between
MarginandPaddingin WPF, and which type do they use? - What is the difference between
Visibility.HiddenandVisibility.Collapsed? - How is an attached property registered, and why are they useful for panels?
- When should you use
LayoutTransforminstead ofRenderTransform? - What does
HorizontalAlignment="Stretch"do, and which is the default alignment?
📘 Lecture 18 — Layout and Transformations in WPF
📖 Overview: This lecture explores layout transformations and panels in WPF. It explains how to apply built-in transforms (rotate, scale, skew, translate, matrix) to UI elements, both as layout and render transforms, and introduces the five main built-in panels (Canvas, StackPanel, WrapPanel, DockPanel, and Grid) for arranging elements. Understanding these concepts is crucial for creating flexible, responsive user interfaces.
🗂️ Topics Covered
The lecture begins by revising size and position properties, then distinguishes between layout transform and render transform. It covers all five built-in transforms: RotateTransform, ScaleTransform, SkewTransform, TranslateTransform, and MatrixTransform, along with their properties and usage. It also explains how to combine transforms using TransformGroup. The second half introduces the five main built-in panels in System.Windows.Controls: Canvas, StackPanel, WrapPanel, DockPanel, and Grid, with detailed examples for Canvas and StackPanel, plus an introduction to VirtualizingStackPanel and WrapPanel.
📝 Lecture Summary
Size and Position Properties Revision
The lecture begins with a refresher on size and position properties. Layout transform is applied before rendering and affects the element's layout position. Render transform is applied after rendering and does not affect layout. The PointConverter is used to specify the origin for transformations.
Built-in Transforms
The five built-in transforms are: RotateTransform, ScaleTransform, SkewTransform, TranslateTransform, and MatrixTransform. Each transform can be applied as a render transform or layout transform, with specific effects.
🔑 Definition — RenderTransformOrigin: A property (e.g., RenderTransformOrigin="0.5,0.5") that specifies the point around which a render transform is applied, relative to the element's size.
📐 Formula: RenderTransformOrigin="x,y" → The origin point expressed as fractions (0 to 1) of the element's width and height.
📌 Example: The button below rotates 45 degrees around its center:
<Button RenderTransformOrigin="0.5,0.5" Background="Orange">
<Button.RenderTransform>
<RotateTransform Angle="45"/>
</Button.RenderTransform>
Rotated 45
</Button>
🔑 Definition — RotateTransform: Rotates an element by a specified Angle (in degrees), with optional CenterX and CenterY properties (default 0). Center is useless for layout transform but useful when grouping render transforms.
📌 Example: Applying rotate transform only to the inner TextBlock:
<Button Background="Orange">
<TextBlock RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform Angle="45"/>
</TextBlock.RenderTransform>
45
</TextBlock>
</Button>
🔑 Definition — ScaleTransform: Scales an element by ScaleX (horizontal) and ScaleY (vertical) factors, with optional CenterX and CenterY origins.
📌 Example: Different scaling modes applied to buttons in a StackPanel:
<StackPanel Width="100">
<Button Background="Red">No Scaling</Button>
<Button Background="Orange">
<Button.RenderTransform>
<ScaleTransform ScaleX="2"/>
</Button.RenderTransform>
X
</Button>
<Button Background="Yellow">
<Button.RenderTransform>
<ScaleTransform ScaleX="2" ScaleY="2"/>
</Button.RenderTransform>
X + Y
</Button>
<Button Background="Lime">
<Button.RenderTransform>
<ScaleTransform ScaleY="2"/>
</Button.RenderTransform>
Y
</Button>
</StackPanel>
💡 Why this matters: Stretch and ScaleTransform only affect the element if more than stretch is applied. Padding is scaled but margin is not. ScaleTransform does not affect ActualHeight, ActualWidth, or RenderSize.
🔑 Definition — SkewTransform: Skews an element horizontally (AngleX) and/or vertically (AngleY), with optional CenterX and CenterY origins (all default 0).
📐 Properties: AngleX — Amount of horizontal skew; AngleY — Amount of vertical skew; CenterX — Origin for horizontal skew; CenterY — Origin for vertical skew.
🔑 Definition — TranslateTransform: Moves an element by X (horizontal) and Y (vertical) amounts. It has no effect as a layout transform, only as a render transform.
📐 Properties: X — Amount to move horizontally (default 0); Y — Amount to move vertically (default 0).
🔑 Definition — MatrixTransform: Uses a single Matrix property (of type System.Windows.Media.Matrix) representing a 3x3 affine transformation matrix. This is the only transform with a type converter to convert a string.
📌 Example: Using MatrixTransform with string shorthand:
<Button RenderTransform="1,0,0,1,10,20"/>
The string "1,0,0,1,10,20" represents a matrix with translation by 10 (X) and 20 (Y).
Combining Transforms
Transforms can be combined by applying both layout and render transforms, or by using a TransformGroup (a Transform-derived class) that calculates the combined matrix automatically.
📌 Example: Combining rotate, scale, and skew transforms in a TransformGroup:
<Button>
<Button.RenderTransform>
<TransformGroup>
<RotateTransform Angle="45"/>
<ScaleTransform ScaleX="5" ScaleY="1"/>
<SkewTransform AngleX="30"/>
</TransformGroup>
</Button.RenderTransform>
OK
</Button>
⚠️ Important: Not all elements can be transformed.
Panels for Layout
Layout of individual elements is finally decided by the parent panel. There are five main built-in panels in System.Windows.Controls:
- Canvas
- StackPanel
- WrapPanel
- DockPanel
- Grid
Content overflow occurs when parents and children cannot agree on the use of available space.
🔑 Definition — Canvas: The most basic panel, rarely used. It uses explicit coordinates (device-independent pixels) relative to any corner of the window. Elements are positioned using attached properties: Left, Top, Right, and Bottom. Default offsets are from top-left.
📌 Example: Positioning buttons in a Canvas using attached properties:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="Buttons in a Canvas">
<Canvas>
<Button Background="Red">Left=0, Top=0</Button>
<Button Canvas.Left="18" Canvas.Top="18" Background="Orange">Left=18, Top=18</Button>
<Button Canvas.Right="18" Canvas.Bottom="18" Background="Yellow">Right=18, Bottom=18</Button>
<Button Canvas.Right="0" Canvas.Bottom="0" Background="Lime">Right=0, Bottom=0</Button>
<Button Canvas.Right="0" Canvas.Top="0" Background="Aqua">Right=0, Top=0</Button>
<Button Canvas.Left="0" Canvas.Bottom="0" Background="Magenta">Left=0, Bottom=0</Button>
</Canvas>
</Window>
💡 Why this matters: If Left and Right are both set, Right is ignored. If Top and Bottom are both set, Bottom is ignored. Z-order (controlled by Canvas.ZIndex) determines which element appears on top when elements overlap.
📌 Example: Z-order with Canvas.ZIndex:
<Canvas>
<Button Canvas.ZIndex="1" Background="Red">On Top!</Button>
<Button Background="Orange">On Bottom with a Default ZIndex=0</Button>
</Canvas>
🔑 Definition — StackPanel: A popular, simple, and useful panel that stacks elements sequentially. It has no attached properties. Orientation can be Horizontal or Vertical (default is Vertical). Default horizontal direction is based on FlowDirection.
🔑 Definition — VirtualizingStackPanel: A virtualizing panel that saves space for offscreen content when data binding (e.g., a ListBox uses it).
🔑 Definition — WrapPanel: Wraps elements to additional rows or columns when there is not enough space. It has no attached properties. Orientation is Horizontal by default. ItemHeight and ItemWidth provide uniform size for all children and are not set by default.
⭐ Key Takeaways
The most critical concepts from this lecture are the distinction between layout transforms (applied before rendering, affecting layout) and render transforms (applied after, not affecting layout), and the properties of all five built-in transforms: RotateTransform (Angle, CenterX, CenterY), ScaleTransform (ScaleX, ScaleY, CenterX, CenterY), SkewTransform (AngleX, AngleY, CenterX, CenterY), TranslateTransform (X, Y — useless as layout transform), and MatrixTransform (the only one with a type converter). You must remember how to combine transforms using TransformGroup and that padding is scaled but margin is not during scaling. For panels, know that Canvas uses explicit coordinates with attached properties (Left, Top, Right, Bottom) and Z-order via Canvas.ZIndex, StackPanel stacks elements with configurable orientation, and WrapPanel wraps content into additional rows or columns. Finally, be aware that content overflow occurs when parent and child disagree on space usage, and that not every element supports transformations.
🧠 Quick Revision Questions
- What is the difference between LayoutTransform and RenderTransform?
- Which transform has a type converter allowing a string shorthand like
"1,0,0,1,10,20"? - In a Canvas, if both
Canvas.LeftandCanvas.Rightare set for a button, which property takes precedence? - How can you combine multiple transforms (e.g., rotate, scale, and skew) into a single render transform?
- What are the five main built-in panels in
System.Windows.Controls?
📘 Lecture 19 — Dock Panel and Grid
📖 Overview: This lecture introduces two essential WPF layout panels: the DockPanel for docking elements to sides, and the Grid for multi-row/column arrangements. Understanding these panels is crucial for building flexible and responsive user interfaces, as they control how child elements are positioned and resized within a window.
🗂️ Topics Covered
The lecture covers the DockPanel panel, its attached Dock property with Top/Left/Right/Bottom values, the LastChildFill property, and the effect of alignment on docked elements. It then introduces the Grid panel as the most versatile layout container, explaining row and column definitions, attached properties (Grid.Row, Grid.Column), spanning (RowSpan, ColumnSpan), sizing modes (Absolute, Auto, Star), and the GridSplitter for interactive resizing.
📝 Lecture Summary
DockPanel
The DockPanel allows easy docking of elements to an entire side of the panel. It uses the Dock attached property which can be set to Left, Right, Top, or Bottom. The last child in the DockPanel fills the remaining space by default, but this behavior can be disabled by setting LastChildFill="False". The order in which elements are added to the DockPanel matters because it affects which corners are occupied by which elements.
🗒️ Code Example (Basic DockPanel):
<DockPanel>
<Button DockPanel.Dock="Top" Background="Red">1 (Top)</Button>
<Button DockPanel.Dock="Left" Background="Orange">2 (Left)</Button>
<Button DockPanel.Dock="Right" Background="Yellow">3 (Right)</Button>
<Button DockPanel.Dock="Bottom" Background="Lime">4 (Bottom)</Button>
<Button Background="Aqua">5</Button>
</DockPanel>
💡 Why this matters: By default, elements in a DockPanel stretch to fill the entire side. Understanding how alignment works is essential to control exact positioning.
🗒️ Code Example (DockPanel with Alignment):
<DockPanel>
<Button DockPanel.Dock="Top" HorizontalAlignment="Right" Background="Red">1 (Top, Align=Right)</Button>
<Button DockPanel.Dock="Left" VerticalAlignment="Bottom" Background="Orange">2 (Left, Align=Bottom)</Button>
<Button DockPanel.Dock="Right" VerticalAlignment="Bottom" Background="Yellow">3 (Right, Align=Bottom)</Button>
<Button DockPanel.Dock="Bottom" HorizontalAlignment="Right" Background="Lime">4 (Bottom, Align=Right)</Button>
<Button Background="Aqua">5</Button>
</DockPanel>
🔑 Definition — DockPanel: A layout panel that docks child elements to one of its four sides (Top, Left, Right, Bottom), with the last child filling the remaining space by default.
📐 Property: DockPanel.Dock attached property → Sets which side of the panel the element should dock to.
📌 Example: Docking a button to the top: <Button DockPanel.Dock="Top">Button Text</Button>
Grid
The Grid is the most versatile layout panel in WPF. It is the default panel in Visual Studio and Expression Blend. It allows arranging elements in multiple rows and columns, similar to an HTML table. Note that there is also a Table class, but it is a FrameworkContentElement, not a UIElement, so it behaves differently.
Elements in a Grid are positioned using the Grid.Row and Grid.Column attached properties, which are zero-based (default is 0,0). Multiple elements can occupy the same cell, in which case they overlap based on z-order (last added is on top). Cells can also be empty.
🗒️ Code Example (Grid with Four Rows and Two Columns):
<Grid Background="lightblue">
<!-- Define four rows: -->
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- Define two columns: -->
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- Arrange the children: -->
<Label Grid.Row="0" Grid.Column="0" Background="Blue" Foreground="White">Start Page</Label>
<GroupBox Grid.Row="1" Grid.Column="0" Background="White" Header="Recent Projects"/>
<GroupBox Grid.Row="2" Grid.Column="0" Background="White" Header="Getting Started"/>
<GroupBox Grid.Row="3" Grid.Column="0" Background="White" Header="Headlines"/>
<GroupBox Grid.Row="1" Grid.Column="1" Background="White" Header="Online Articles">
<ListBox>
<ListBoxItem>Article #1</ListBoxItem>
<ListBoxItem>Article #2</ListBoxItem>
<ListBoxItem>Article #3</ListBoxItem>
<ListBoxItem>Article #4</ListBoxItem>
</ListBox>
</GroupBox>
</Grid>
🔑 Definition — Grid: A layout panel that arranges child elements in a matrix of rows and columns, defined by RowDefinitions and ColumnDefinitions.
📐 Properties: Grid.Row (default 0), Grid.Column (default 0), Grid.RowSpan (default 1), Grid.ColumnSpan (default 1) → Used to position and span elements across cells.
📌 Example: Placing a button in row 2, column 1: <Button Grid.Row="2" Grid.Column="1">Click Me</Button>
Grid Sizing Modes
Grid rows and columns can be sized using three modes:
- Absolute sizing: Fixed size in device-independent pixels (e.g.,
Width="100"). Does not grow or shrink. - Auto sizing: Size to fit content (e.g.,
Width="Auto"). - Proportional or Star sizing: Grows or shrinks to fill remaining space (e.g.,
Width="*"orWidth="2*").
When using star sizing, * takes all remaining space. If multiple rows/columns use *, they divide the remaining space proportionally. For example, 2* is twice as wide as * (which equals 1*). The remaining space is calculated after absolute and auto-sized rows/columns are accounted for. Default width and height for Grid rows/columns are *.
🗒️ GridLength in Procedural Code:
GridLength length = new GridLength(100); // Absolute pixel value
GridLength length = new GridLength(0, GridUnitType.Auto); // Auto size
GridLength length = new GridLength(100, GridUnitType.Pixel); // Absolute pixel
GridLength length = new GridLength(2, GridUnitType.Star); // 2* proportional
🔑 Definition — GridLength: A structure used to specify the size of a Grid row or column, supporting Absolute (pixel), Auto, or Star (proportional) sizing.
📐 Formula: Width="2*" → Takes twice the space of Width="*" (1*) from the remaining available space.
📌 Example: In a Grid with two columns: ColumnDefinition Width="*" and ColumnDefinition Width="2*" → The second column is twice as wide as the first, dividing the remaining space in a 1:2 ratio.
GridSplitter
The GridSplitter is used for interactive resizing of Grid rows and columns by the user. Any number of GridSplitters can be added to a Grid. It can be used to resize an entire row or column. At least one cell resizes, and the behavior of other cells depends on whether they use absolute or proportional sizing.
The GridSplitter fits in one cell, but its behavior affects the entire row or column, so it is better to use span to cover the entire row/column. Which cells are affected depends on the GridSplitter's alignment values. Default HorizontalAlignment is Right, and default VerticalAlignment is Stretch. Reasonable use requires stretching in one dimension.
When all cells are proportionally sized, changes to the splitter modify the coefficients accordingly. When cells have absolute sizing, only the top or left of the cells changes, and remaining cells are pushed down or right.
The GridSplitter has a ResizeDirection property (default Auto, or can be set to Rows or Columns) and a ResizeBehavior property for explicit control. ResizeDirection takes effect only when stretching in both directions. ResizeBehavior defaults to BasedOnAlignment and can be set to PreviousAndCurrent, CurrentAndNext, or PreviousAndNext to control which two rows/columns are directly affected by resizing. The best practice is to place the GridSplitter in its own auto-sized row or column so it does not overlap existing content.
🔑 Definition — GridSplitter: A control that allows users to interactively resize Grid rows and columns at runtime.
📐 Properties: ResizeDirection (Auto, Rows, Columns), ResizeBehavior (BasedOnAlignment, PreviousAndCurrent, CurrentAndNext, PreviousAndNext).
📌 Example: Add a GridSplitter between two columns: Place a <GridSplitter/> in its own auto-sized column between the two columns to allow resizing.
⭐ Key Takeaways
The DockPanel is ideal for docking toolbars, menus, or status bars to the edges of a window, with the last child filling the remaining space—a pattern often used in top-level interface layouts. The Grid is the most powerful and default layout panel in WPF, supporting multi-row/column arrangements with three sizing modes: Absolute (fixed pixels), Auto (size to content), and Star/Proportional (dynamic resizing relative to available space). Understanding how to use attached properties like Grid.Row, Grid.Column, Grid.RowSpan, and Grid.ColumnSpan is essential for placing and spanning elements. The GridSplitter provides interactive resizing, and its behavior depends on cell sizing modes and alignment; best practice is to place it in its own auto-sized row or column. Finally, the Grid is a superset of the StackPanel in functionality, and the order of elements matters both in DockPanel (for corner allocation) and Grid (for z-order overlap).
🧠 Quick Revision Questions
- What are the four possible values for the
DockPanel.Dockattached property, and what happens to the last child by default? - How do you define rows and columns in a Grid using XAML? What are the attached properties used for positioning?
- What are the three sizing modes for Grid rows and columns? Explain how star sizing distributes space when multiple rows/columns use it.
- How does a GridSplitter affect cells with absolute sizing versus proportional (star) sizing?
- What is the purpose of the
ResizeBehaviorproperty on a GridSplitter, and what are its possible values?
📘 Lecture 20 — Let’s revise grid.
📖 Overview: This lecture covers advanced grid layout features in WPF, focusing on the
SharedSizeGroupproperty which allows multiple rows or columns to maintain equal sizing when resized viaGridSplitter. It also addresses content overflow handling strategies including clipping, scrolling, scaling, wrapping, and trimming, and introduces theScrollViewerandViewboxcontrols for managing content that exceeds available space.
🗂️ Topics Covered
The lecture begins by revising Grid layout with GridSplitter, then introduces SharedSizeGroup and IsSharedSizeScope for synchronized column/row sizing across grids. It then covers five methods for handling content overflow: clipping, scrolling, scaling, wrapping, and trimming. Specific controls discussed include the ScrollViewer for adding scroll bars and the Viewbox for scaling content to fill available space, with properties like Stretch and StretchDirection. The lecture concludes with a practical example of creating a Visual Studio-like interface.
📝 Lecture Summary
[Let’s revise grid.]
The lecture starts with a basic Grid example demonstrating ColumnDefinitions with Width="Auto" and using GridSplitter to allow interactive column resizing. This establishes the foundation for more advanced grid features.
🔑 Definition — GridSplitter: A control that allows users to resize columns or rows of a Grid at runtime by dragging the splitter bar.
[SharedSizeGroup enables multiple rows/cols to remain the same width/height when length changed via GridSplitter.]
The SharedSizeGroup property is introduced to solve the problem of maintaining equal column widths across different grid cells when one column is resized. IsSharedSizeScope is a boolean attached property of Grid that enables SharedSizeGroup to work across multiple grids, meaning size groups can be shared under a common parent with IsSharedSizeScope="True". Without this, the shared size groups would not be recognized across separate grid instances.
🔑 Definition — SharedSizeGroup: A property that links multiple columns or rows together so they maintain the same width or height, even when resized using a GridSplitter.
📐 Formula: <Grid IsSharedSizeScope="True"> → enables SharedSizeGroup to work across multiple grids under this parent.
📌 Example:
<Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="mygroup"/>
<ColumnDefinition/>
<ColumnDefinition SharedSizeGroup="mygroup"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Background="Red" ... />
<GridSplitter Grid.Column="0" Width="5"/>
<Label Grid.Column="1" Background="Orange" ... />
<Label Grid.Column="2" Background="Yellow" ... />
</Grid>
Here, columns 0 and 2 share the same size group "mygroup". When the GridSplitter at column 0 resizes column 0, column 2 automatically adjusts to maintain the same width. The IsSharedSizeScope="True" is on the outer Grid.
💡 Why this matters: This enables creating consistent, proportional layouts where resizing one section automatically adjusts related sections, essential for tools like IDEs or dashboards.
[Content overflow can be dealt with Clipping, Scrolling, Scaling, Wrapping and Trimming.]
This section details five strategies for handling content that exceeds the available space of a container. Wrapping is already seen with text, but for non-text elements, only WrapPanel enables wrapping behavior. Trimming is an intelligent form of clipping specifically for text in TextBlock and AccessText, where the TextTrimming property can be set to None (default), CharacterEllipsis, or WordEllipsis. Clipping is the default behavior for most panels, where content beyond edges is simply cut off. The ClipToBounds property on all UIElement controls determines whether they can draw outside their bounds, but no control can draw outside the window or page. Canvas and UniformGrid do not clip unless ClipToBounds is set to True. Button has this property set to False by default. Placing a Canvas inside a Grid cell can avert clipping. Crucially, clipping occurs before RenderTransform, so you cannot shrink content back into view after it has been clipped.
🔑 Definition — TextTrimming: A property (CharacterEllipsis or WordEllipsis) that replaces clipped text with an ellipsis (“...”) at the character or word boundary.
🔑 Definition — ClipToBounds: A UIElement property that, when True, prevents the element from drawing content beyond its own boundaries.
📐 Property: TextTrimming="CharacterEllipsis" or "WordEllipsis" (default is None).
[System.Windows.Controls.ScrollViewer control can be used for scrolling.]
The ScrollViewer control is introduced as the standard way to add scroll bars to a region of content. Its content property is set to the element that needs scrolling, and VerticalScrollBarVisibility and HorizontalScrollBarVisibility properties control the display of scroll bars.
📌 Example:
<Window Title="Using ScrollViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ScrollViewer>
<StackPanel>
... <!-- Content that may overflow -->
</StackPanel>
</ScrollViewer>
</Window>
[Scaling – Viewbox]
While scrolling is more common, some scenarios (e.g., card games) require scaling. ScaleTransform works relative to an element’s own size, not the available container size. The Viewbox control is a Decorator class (like Border) that acts like a panel but can only have one child. It stretches its child content to fill the available space. The Stretch property controls how stretching occurs: None (no stretching), Fill (distort to fill), Uniform (maintain aspect ratio, default), and UniformToFill (cropped). The StretchDirection property controls the direction of scaling: UpOnly, DownOnly, or Both (default).
🔑 Definition — Viewbox: A WPF control that scales its single child element to fill the available space, preserving or distorting aspect ratio based on the Stretch and StretchDirection properties.
📐 Properties:
Stretch="Uniform"(default) — scales proportionally to fill the available space without cropping.Stretch="Fill"— stretches non-uniformly to completely fill the space.StretchDirection="DownOnly"— only shrinks content, never enlarges it.
📌 Example:
<Window Title="Using Viewbox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Viewbox Stretch="Uniform" StretchDirection="DownOnly">
<StackPanel>
... <!-- Content scales to fit the window while maintaining proportions and never enlarging beyond original size -->
</StackPanel>
</Viewbox>
</Window>
💡 Why this matters: Viewbox is essential for creating responsive UIs where content must fit different screen sizes without distortion, such as diagrams, charts, or game interfaces.
⭐ Key Takeaways
The most critical points to remember are: (1) SharedSizeGroup with IsSharedSizeScope enables synchronized column/row resizing across multiple grids, essential for creating consistent layouts; (2) Content overflow can be handled by five methods: clipping (default with ClipToBounds), scrolling (ScrollViewer), scaling (Viewbox), wrapping (WrapPanel for non-text), and trimming (text with TextTrimming); (3) The ScrollViewer control wraps content and provides scroll bars controlled by VerticalScrollBarVisibility and HorizontalScrollBarVisibility; (4) The Viewbox control scales its single child using the Stretch property (None, Fill, Uniform, UniformToFill) and StretchDirection property (UpOnly, DownOnly, Both); (5) Clipping occurs before RenderTransform, so you cannot recover clipped content with a transform — use Viewbox or ScrollViewer instead.
🧠 Quick Revision Questions
- What is the purpose of the
SharedSizeGroupproperty in a Grid, and what must be set toTrueto enable it across multiple grids? - List the five methods for handling content overflow in WPF.
- What is the difference between
TextTrimming="CharacterEllipsis"andTextTrimming="WordEllipsis"? - Which WPF control is used to add scrolling to a region, and which properties control scroll bar visibility?
- If you want to scale a UI element to fill its parent container while maintaining its aspect ratio, which
Stretchvalue should you use in aViewbox?
📘 Lecture 21 — Application with Collapsible, Dockable, Resizable Panes and Input Events
📖 Overview: This lecture covers the implementation of a complex WPF interface featuring collapsible, dockable, and resizable panes using multiple Grid layers and SharedSizeGroup. It then introduces the concept of routed events, explaining their routing strategies, how they differ from .NET events, and demonstrates them with keyboard and mouse event handling.
🗂️ Topics Covered
The lecture explains how to build a multi-pane interface using three overlapping Grid layers with SharedSizeGroup to maintain synchronization when docked. It covers the XAML structure with DockPanel, rotating button bars, and gridsplitters for resizing. The code-behind manages pane docking/undocking through visibility toggling, cloned column definitions, and Z-order manipulation. The second half introduces routed events as an extra layer on .NET events, covering tunneling, bubbling, and direct routing strategies, with event handlers that access Source and OriginalSource properties.
📝 Lecture Summary
Building a Multi-Pane Interface with Grid Layers
Because splitters are needed, a Grid is a reasonable approach. The interface uses three independent Grids to allow overlapping. SharedSizeGroup keeps them in sync when docked. When docking, cells are added or removed. Z-order between layer 1 and layer 2 ensures that the undocked pane appears on top. All three grids are placed in another Grid of a single row and column.
The XAML structure begins with a DockPanel containing a Menu docked at the top. A StackPanel named "buttonbar" is docked to the right edge, with a RotateTransform applied at 90 degrees to display buttons vertically. Inside the DockPanel, a parent Grid named "parentgrid" has IsSharedSizeScope="True" with two column definitions (5* and 364*).
🔑 Definition — SharedSizeScope: A property that allows multiple Grid elements to share the same column or row sizing based on a shared size group name, keeping them synchronized.
Layer 0 is the main content area with Grid.ColumnSpan="2". Layer 1 and Layer 2 each contain a column definition with SharedSizeGroup ("column1" and "column2" respectively) and Width="Auto". Each has a Grid in column 1 containing a header with a pin button and pane-specific content, plus a GridSplitter of Width 5 with HorizontalAlignment="Left" for resizing.
📐 Formula: SharedSizeGroup → Columns with the same SharedSizeGroup name across different Grids maintain the same width automatically.
Code-Behind: Dock and Undock Logic
The code-behind class mainwindow inherits from Window. Three dummy ColumnDefinition objects are created in the constructor for managing docked columns: column1cloneforlayer0, column2cloneforlayer0, and column2cloneforlayer1, each assigned a SharedSizeGroup.
The Dockpane method toggles a pane from undocked to docked state. It hides the corresponding pane button (Visibility.Collapsed), changes the pin image source to "pin.gif", and adds the appropriate cloned column to layer 0. If the other pane is already docked, it also adds the cloned column to layer 1.
The Undockpane method reverses the process. It shows the pane layer (Visibility.Visible), reveals the pane button, changes the pin image to "pinhorizontal.gif", and removes the cloned columns from layers 0 and 1. The Remove method silently ignores columns that are not present.
💡 Why this matters: The cloned column approach allows the docked pane to occupy real column space when docked, while the SharedSizeGroup ensures the column widths remain consistent between root states.
Mouse Enter Event Handling for Auto-Show and Z-Order
When the mouse enters a pane button, the corresponding layer becomes visible (Visibility.Visible). The Z-order is adjusted using Grid.SetZIndex to ensure the active pane is on top (ZIndex = 1) while the other is at ZIndex = 0. If the other pane is undocked (its button is visible), it gets collapsed to prevent overlap.
When the mouse enters Layer 0 (the main content area), any undocked panes are collapsed. Similarly, when entering a docked pane, the other undocked pane is collapsed to maintain focus.
Introduction to Routed Events
Routed events are an extra layer on top of .NET events, similar to how dependency properties extend .NET properties. They can travel up or down the visual or logical tree, helping applications remain oblivious to the visual tree structure. For example, a Button exposes a Click event based on MouseLeftButtonDown and KeyDown, but actually a visual child like ButtonChrome or TextBlock fires the event. The event travels up the tree, and the Button eventually sees it.
Routed events are an implementation feature (not a language feature) supported by XAML. They use public static RoutedEvent fields, conventionally suffixed with "Event", registered in a static constructor, with a .NET event wrapper (which should not perform additional logic).
🔑 Definition — RoutedEvent: An event that can be routed through multiple handlers in a visual or logical tree, supporting tunnel, bubble, or direct strategies.
Registering a Routed Event
In the Button class, the Click event is declared as public static readonly RoutedEvent ClickEvent. In the static constructor, it is registered using EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Button)). The .NET event wrapper uses AddHandler and RemoveHandler from UIElement. The event is raised in OnMouseLeftButtonDown using RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this)).
Routing Strategies
Three routing strategies exist:
- Tunneling: Event travels from the root element down to the source element, or until marked as handled. Tunneling events are prefixed with Preview (e.g., PreviewMouseMove comes before MouseMove).
- Bubbling: Event travels from the source element up to the root, or until marked as handled. Most input events use bubbling.
- Direct: Event is raised only on the source element, similar to standard .NET events, but still participates in routed event features like event triggers.
🔑 Definition — RoutingStrategy.Bubble: The event starts at the source element and travels upward through the visual tree to the root. 🔑 Definition — RoutingStrategy.Tunnel: The event starts at the root element and travels downward through the visual tree to the source.
Event Handler Parameters
Handlers for routed events receive a System.Object parameter (the sender to which the handler was attached) and System.EventArgs containing:
- Source: The element in the logical tree that originally raised the event
- OriginalSource: The element in the visual tree that raised the event (e.g., a TextBlock on top of a Button)
- RoutedEvent: The actual routed event object (e.g., Button.ClickEvent), useful when the same handler is used for multiple events.
By convention, actions are only taken on the bubbling event, so the tunneling (Preview) event gives a chance to cancel or modify the event (e.g., using PreviewKeyDown for restrictive input in a TextBox).
Example: Mouse Right Button Down Handler
The example shows an About dialog window with the MouseRightButtonDown event attached to the Window. The event handler aboutdialog_mouserightbuttondown displays information about the event in the window title: "Source = " plus the type name of e.Source, "OriginalSource = " plus the type name of e.OriginalSource, and the timestamp.
It then casts the Source to a Control and toggles a border around it — setting a 5-pixel thick black border if none exists, or removing it otherwise. This demonstrates how the same handler can respond to events coming from any child element in the visual tree.
⭐ Key Takeaways
A student must understand how to implement a multi-pane docking interface using Grid layers with SharedSizeGroup, cloned column definitions, and Z-order management for overlapped elements. The concept of routed events is fundamental — they travel through the visual/logical tree using tunneling (Preview prefix, root to source) or bubbling (source to root) strategies, allowing parent elements to handle events from their children. The Source property refers to the logical tree element that raised the event, while OriginalSource refers to the visual tree element. Most input events like mouse and keyboard follow this routed event pattern, enabling simplified event handling without custom code on inner content.
🧠 Quick Revision Questions
- Why are three overlapping Grid layers used in this application instead of a single Grid with nested elements?
- What is the purpose of
SharedSizeGroupandIsSharedSizeScopein the docking interface implementation? - How does the
Dockpanemethod ensure that a docked pane occupies the correct column space in both layers? - What is the difference between tunneling and bubbling routing strategies, and how does the naming convention distinguish them?
- In the mouse right button down example, why does
OriginalSourcediffer fromSource, and what scenario would cause this difference?
📘 Lecture 22 — Attached Events, Keyboard, Mouse, Stylus & Touch Events
📖 Overview: This lecture continues the discussion of routed events and introduces attached events, which allow windows to handle events from elements that don't define them. It then covers keyboard events, mouse events (including drag-and-drop and mouse capturing), stylus input, and multi-touch events available on Windows 7 or later.
🗂️ Topics Covered
The lecture reviews code from the previous session, introduces attached events with practical examples, explains generic event handling using delegate contravariance, covers keyboard events and keyboard focus, details mouse events including mouse capture and drag-and-drop, discusses stylus input, and introduces basic and manipulation touch events for multi-touch hardware.
📝 Lecture Summary
Review of Previous Code
The lecture begins with a code example demonstrating how to handle the MouseRightButtonDown event. The handler displays event information from MouseButtonEventArgs, including the Source and OriginalSource types, and toggles a border thickness on the source control. The code shows that listbox items never receive events when clicked, and setting border on a Button has no effect since it has no border element. Handling a halted event can only be done from procedural code.
Attached Events
Attached events are events that can bubble and tunnel through elements that have not defined the event themselves, similar to property value inheritance and attached properties. The lecture presents an example where a Window handles ListBox.SelectionChanged and Button.Click events, even though the Window does not define these events. The XAML markup attributes listbox.selectionchanged and Button.Click are valid because the compiler sees these events defined in the ListBox and Button at runtime, and AddHandler is called directly.
The equivalent procedural code is:
this.AddHandler(ListBox.SelectionChangedEvent,
new SelectionChangedEventHandler(listbox_selectionchanged));
this.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));
🔑 Definition — Attached Events: Events that can be handled by parent elements that do not define the event themselves, using AddHandler to connect to the event.
📌 Example: A Window handles both ListBox.SelectionChanged and Button.Click events through XAML attributes, where the event names are prefixed with the type that defines them (e.g., listbox.selectionchanged).
Generic Event Handling with Delegate Contravariance
Theoretically, one handler can handle all events using delegate contravariance, where arguments can be of a base type than the delegate. A single genericHandler method can check the e.RoutedEvent property to determine which event occurred and cast the event arguments accordingly.
void genericHandler(object sender, RoutedEventArgs e)
{
if (e.RoutedEvent == Button.ClickEvent)
{
MessageBox.Show("You just clicked " + e.Source);
}
else if (e.RoutedEvent == ListBox.SelectionChangedEvent)
{
SelectionChangedEventArgs sce = (SelectionChangedEventArgs)e;
if (sce.AddedItems.Count > 0)
MessageBox.Show("You just selected " + sce.AddedItems[0]);
}
}
🔑 Definition — Delegate Contravariance: The ability for an event handler to accept arguments of a base type (e.g., RoutedEventArgs) rather than the specific derived type (e.g., SelectionChangedEventArgs).
Keyboard Events
Keyboard events include KeyDown, KeyUp, and their Preview versions. KeyEventArgs contains properties such as Key, ImeProcessedKey, DeadCharProcessedKey, SystemKey, IsUp, IsDown, IsToggled, KeyStates, IsRepeat, and KeyboardDevice. The static class System.Windows.Input.Keyboard and its PrimaryDevice property are accessible everywhere.
The lecture provides examples for detecting Alt+A key combinations:
Using bitwise AND to check modifiers:
if ((e.KeyboardDevice.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt &&
(e.Key == Key.A || e.SystemKey == Key.A))
{
// Alt+A has been pressed, potentially also with Ctrl, Shift, and/or Windows
}
Using equality check for exact modifier:
if (e.KeyboardDevice.Modifiers == ModifierKeys.Alt &&
(e.Key == Key.A || e.SystemKey == Key.A))
{
// Alt+A and only Alt+A has been pressed
}
You can use KeyboardDevice.IsKeyDown to check if specific keys like left or right Alt are pressed.
Keyboard Focus
Keyboard focus determines which UI element receives keyboard input. The UIElement.Focusable property (true by default), along with events like FocusableChanged, and read-only properties IsKeyboardFocused and IsKeyboardFocusWithin, control focus behavior. To set focus, use Focus or MoveFocus. Related events include IsKeyboardFocusedChanged, IsKeyboardFocusWithinChanged, GotKeyboardFocus, LostKeyboardFocus, PreviewGotKeyboardFocus, and PreviewLostKeyboardFocus.
🔑 Definition — Keyboard Focus: The element that currently receives keyboard input, determined by the Focusable property and focus management methods.
💡 Why this matters: Understanding keyboard focus is essential for accessibility and proper keyboard navigation in applications.
Mouse Events
Mouse events include:
- MouseEnter and MouseLeave (for rollover effects, though triggers with
IsMouseOverare preferred) - MouseMove and PreviewMouseMove
- MouseLeftButtonDown, MouseRightButtonDown, MouseLeftButtonUp, MouseRightButtonUp, plus the generic MouseDown and MouseUp
- All six events have Preview versions
- MouseWheel and PreviewMouseWheel
Key behaviors:
- If
Visibility=Collapsed, no mouse events are generated - If
Opacity=0, all events are still generated - Null background/brush produces areas with no events, while Transparent does generate events (they look the same but behave differently for hit testing)
MouseEventArgs has five properties of type MouseButtonState (Pressed or Released): LeftButton, RightButton, MiddleButton, XButton1, and XButton2. The GetPosition function returns a Point with X and Y properties — pass null for screen-relative position or an element for position relative to that element.
MouseWheelEventArgs (derived from MouseEventArgs) adds a Delta property. The 12 mouse up/down events get MouseButtonEventArgs, which adds ChangedButton, ButtonState (for the changed button), and ClickCount properties. The Button base class raises a MouseDoubleClick event by checking ClickCount in MouseLeftButtonDown.
📐 Formula: e.GetPosition(null) → returns mouse position relative to the screen. e.GetPosition(element) → returns mouse position relative to that element.
Drag and Drop
Drag and drop events include DragEnter, DragOver, DragLeave, with Preview versions, Drop and PreviewDrop, QueryContinueDrag and PreviewQueryContinueDrag. Drag and drop works with clipboard content, not elements themselves. It is enabled by setting AllowDrop=true.
These events provide DragEventArgs with methods like GetPosition, and properties Data, Effects, and AllowedEffects (Copy, Move, Link, Scroll, All, None), and KeyStates (LeftMouseButton, RightMouseButton, MiddleMouseButton, ShiftKey, ControlKey, AltKey, or None). Continue events are raised when keyboard or mouse state changes during the operation, and QueryContinueDragEventArgs has KeyStates, EscapePressed, and Action (Continue, Drop, Cancel).
Mouse Capturing
Mouse capturing is important for drag-and-drop operations. When a UI element captures the mouse, it continues receiving mouse events even if the mouse moves outside the element or under another element. Properties include IsMouseCaptured and IsMouseCaptureWithin, and events include GotMouseCapture, LostMouseCapture, IsMouseCaptureChanged, and IsMouseCaptureWithinChanged. Inside MouseMove, you can use a layout or RenderTransform to move the element.
🔑 Definition — Mouse Capture: A state where a UI element receives all mouse events even when the mouse pointer is outside the element's bounds.
Stylus Events
The Stylus can behave like a mouse but has higher resolution, can be inverted, detects whether it's in air or not, and provides pressure sensitivity.
Multi-Touch Events
Multi-touch events are available on Windows 7 or later with multi-touch hardware. Two categories exist:
Basic Touch Events: TouchEnter and TouchLeave, TouchMove and PreviewTouchMove, TouchDown, TouchUp, PreviewTouchDown and PreviewTouchUp, GotTouchCapture and LostTouchCapture. With multiple fingers, events are raised for each finger separately. For the first finger, mouse events are also generated. TouchEventArgs has GetTouchPoint, GetIntermediateTouchPoints, TouchDevice. TouchPoint has Position, Size, Bounds, TouchDevice, Action (Down, Up, Move). Each finger has its own TouchDevice identified by an Id property. Some notebooks support only two simultaneous touch points. A lower-level FrameReported event (which is not even routed) is also raised.
Manipulation Events: Used for panning, rotating, and zooming. These are easy to apply with a Transform. While swiping is easy with basic events, rotation and other gestures are difficult with previous events and lack consistency in behavior between applications. Therefore, using manipulation events is preferred.
⭐ Key Takeaways
Attached events allow a window or parent element to handle events from child elements that define those events, using AddHandler or XAML attributes with type-prefixed event names. Delegate contravariance enables a single generic event handler to process multiple event types by checking the RoutedEvent property. Keyboard events include KeyDown and KeyUp, while mouse events include a comprehensive set of down, up, move, enter, leave, and wheel events — with important differences between null and transparent backgrounds for hit testing. Mouse capturing is essential for drag-and-drop operations to ensure continuous event delivery. Finally, multi-touch events provide both basic touch handling and higher-level manipulation events for panning, rotating, and zooming, with the latter being preferred for consistency across applications.
🧠 Quick Revision Questions
- What is an attached event, and how does a window handle an event like
ListBox.SelectionChangedthat it doesn't define? - How can a single event handler process both
Button.ClickandListBox.SelectionChangedevents? What is delegate contravariance? - What is the difference between checking keyboard modifiers with
&(bitwise AND) versus==(equality)? - Why does a
nullbackground behave differently from aTransparentbackground for mouse hit testing? - What makes manipulation events (for panning, rotating, zooming) preferred over basic touch events for complex gestures?