Welcome to MSDN Blogs Sign in | Join | Help

Errata: WPF Recipes in C# 2008 4-3. Add Properties to a User Control

I am an avid reader. We can always learn something new in reading a new book. But no book can be perfect, esp. technical books on new technologies. So I would take notes when I noticed some inaccurate, misgudied or incorrect discussions or examples. And starting from this year, I will share some notes in my blog.

In WPF Recipes in C# 2008, chapter 4, example 3: Add Properties to a User Control, it demostrates how to use DependencyProperty to interact with a custom PageNumberControl. PageNumberControl defines two Dependency Properties: Current and Total. And the definition for Total looks like this:

        public int Total
        {
            get
            {
                return (int) GetValue(TotalProperty);
            }
            set
            {
                if(value >= Current
                   && value >= 0)
                {
                    SetValue(TotalProperty, value);
                }
            }
        }

        public static readonly DependencyProperty TotalProperty =
            DependencyProperty.Register("Total",
                                        typeof(int),
                                        typeof(PageNumberControl),
                                        new PropertyMetadata(0));

In CLR property Total setter, the code above does some sanity check: (1) Total value should not be negative. (2) Total value should not be smaller than Current value. Such constraint makes sense, but we should not place the check in CLR property setter.

That is because CLR property should always be a thin wrapper of the underlying Dependency Property. Users can always set TotalProperty to any int value with such code pageNumberControl1.SetValue(PageNumberControl.TotalProperty, -100); and the CLR property setter is not involved at all.

What's more, WPF features such as data binding, style and animations work on Dependency Property, rather than CLR property.

The fix is to use ValidValueCallback (for "Total value should not be negative number") and CoerceValueCallback (for "Total value should not be smaller than Current value") when we register DependencyProperty.

The same applies to Current property.

(This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm)
Posted by zhanbos | 1 Comments
Filed under:

WPF Visual Designer (code name Cider) Tip 4: Select Parent Control

A common task is to select parent control of the currently selected control or controls.

When only one control is selcted, there are at least three ways to achieve so.

  1. You can choose the parent control directly on Designer, or via PathControl/Document Outline, etc.
  2. You can use the current control's Context Menu to "Select" its immediate parent, its grandparent, and so on, until root element.
  3. You can press "ESC" key to have parent control selected, and "ESC" again to go up parent chain, until root element.

The third way is especially useful when you want to ensure that root is selected (just keep pressing "ESC" until no selection change occurs). Also, it works for multiple controls. In contrast, designer Context menu will not show "Select" sub menu for multiple controls.

 (This posting is provided "AS IS" with no warranties, and confers no rights.)

Posted by zhanbos | 0 Comments
Filed under:

WPF Visual Designer (code name Cider) Tip 3: Nudge vs. Move

Applies to Visual Studio 2008 Beta2.  

User can also change controls' positions via keyboard nudge: Select one or more controls, and then press Up, Down, Left, or Right key. Unlike Move (or move via mouse to be more specific), nudge does not change controls' parent.

In this sense, nudge is like resize, as has discussed in previous tip.

(This posting is provided "AS IS" with no warranties, and confers no rights.)

Posted by zhanbos | 1 Comments
Filed under:

WPF Visual Designer (code name Cider) Tip 2: Resize vs. Move

Take a look at the following XAML:

    <Grid Name="mainGrid">

        <Grid Margin="17,20,132,130" Name="grid1">

            <Button Height="23" Margin="10,10,44,0" Name="button1" VerticalAlignment="Top">Button</Button>

        </Grid>

        <Grid Height="98" Margin="118,0,12,17" Name="grid2" VerticalAlignment="Bottom">

            <Button Height="23" Margin="10,10,63,0" Name="button2" VerticalAlignment="Top">Button</Button>

        </Grid>

    </Grid>

Supposing we want to achieve such effect that button1 is on top of button2, we have two options:

  1. We can select button1, and move to the top of button2.
  2. We can also resize button1much larger so that its bottom right is on button2's bottom right; then, resize on top left.

Is there any differences in resulting XAML?

The answer is: Move will change button1's Parent, yet Resize won't.

(This posting is provided "AS IS" with no warranties, and confers no rights.)

Posted by zhanbos | 3 Comments
Filed under:

WPF Visual Designer (code name Cider) Tip 1: Turn Snapline Off

Applies to Visual Studio 2008 Beta2.  

By default, Snapline is on when you perform such actions as move or resize control, or move grid lines. But there are two options to turn snapline off.

  • To keep the snapping behavior without showing snaplines, you use SHIFT+CTRL keyboard modifier. (Note1)
  • To turn off snapping behavior altogether (and of course see no snaplines), you use ALT keyboard modifier. (Note 2)

Note 1: SHIFT+CTRL modifier also turns on Alignment Visual with which you can easilly tell what the HorizontalAlignment and VerticalAlignment value are.

Note 2: If you release ALT key before mouse is released, you are likely to hear a Beep. If you do not want to hear it all the time, release mouse first and then ALT key.

(This posting is provided "AS IS" with no warranties, and confers no rights.)

Posted by zhanbos | 1 Comments
Filed under:

Cider: primitive split View on XAML files

Applies to: Cider CTP June 2006.

With split view on a XAML file, developers should be able to see both the Designer and XAML at the same time. Moreover, developers should be able to use Designer and XAML editor in any way, and the two views are always in sync.

While current CTP build of Cider does not explicitly support Split View yet, we can use VS2005's Window management support to view both the Designer and XAML altogether.

The tip is to right click on a XAML file from Solution Explorer, Select "Open With..." from its context menu, and then choose "XML Editor" in the dialog. (The default "Open With ..." option is "WPF Designer (Cider)". )

Now if you already have the designer for the same XAML file open, (if not, double click the file from Solution Explorer), just click and Drag the tab for the XML editor towards the bottom of designer window (where "Design", "XAML" and "Source" buttons reside). Release mouse when you see a window highlight of half the designer.

Of course, this split view is very primitive. Two views are not in sync unless you switch the focus between the two. Explicitly saving the change within designer view will also update XAML view, but NOT vice versa.

If you have your wish list on what a real split view should look or function like, feel free to comment on this blog.

(This posting is provided "AS IS" with no warranties, and confers no rights.)

Posted by zhanbos | 1 Comments

Locally set value takes precedence over Style value

If a DependencyProperty’s value is locally set, this value takes precedence over whatever value specified in Style. For example, the only button’s Background is LightGreen instead of LightBlue:

<Grid xmlns="http://schemas.microsoft.com/winfx/avalon/2005" >
    <Grid.Resources>
        <Style>
            <Button Background="LightBlue"/>
        </Style>
    </Grid.Resources>
    <Button Content="Demo" Background="LightGreen"/>
</Grid>

The first example is indeed very obvious. Now take a look at the next example, in which PropertyTrigger within Style is used. When you move mouse over the only button, what will be its Background?

<Grid xmlns="http://schemas.microsoft.com/winfx/avalon/2005" >
    <Grid.Resources>
        <Style>
            <Button />
            <Style.VisualTriggers>
                <PropertyTrigger Property="IsMouseOver" Value="True">
                    <Set PropertyPath="Background" Value="LightBlue"/>
                </PropertyTrigger>
            </Style.VisualTriggers>
        </Style>
    </Grid.Resources>
    <Button Content="Demo" Background="LightGreen"/>
</Grid>

Because locally set value takes precedence over style value, the Background remains LightGreen when IsMouseOver is true. 

To see the effect of PropertyTrigger, move Background="LightGreen" into Style definition.

(This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm)

Posted by zhanbos | 2 Comments
Filed under:

CLR Event vs. Routed Event: Two Demo Applications

As you may have already known, you can define and use Dependency Property as well as CLR Property in Avalon. (If you want to know more about Avalon’s property system, stay tuned in this blog.) Same thing can be said about CLR Event and Routed Event. By supporting routed event in Avalon, the parent element can participate in events sourcing from its child elements. This blog post presents two applications to show the differences between CLR event and RoutedEvent.

 

The first application is a Windows Form 2.0 application. WinForm only uses CLR event. System.Windows.Forms.Control.MouseMove event, for example, occurs when the mouse pointer is moved over the control. That control can choose to handle it by registering an event handler. But if it does not handle it, its parent control will not be notified that MouseMove event occurs for one of its child controls.

 

Compile the complete WinForm code below into a WinExe application and run it. Event handler outerPanel_MouseMove prints out the timestamp for each MouseMove event received by outerPanel(Green area). As you can see, when the mouse is moved into innerPanel (Blue area), event handler is not called. That makes sense for CLR event since the event now occurs for innerPanel instead of outerPanel.

 

Of course, to respond to MouseMove on innerPanel as well, you can hook up event handler on innerPanel itself. You do not need to do that in Avalon with Routed Event, though.

 

using System;

using System.Collections.Generic;

using System.Windows.Forms;

 

namespace ZhanboBlog.Demo

{

  static class Program

  {

    [STAThread]

    static void Main()

    {

      Application.EnableVisualStyles();

      Application.Run(new DemoForm());

    }

  }

 

  internal class DemoForm : Form

  {

    internal DemoForm()

    {

      InitializeComponent();

    }

 

    private void InitializeComponent()

    {

      this.Text = "Zhanbo Blog Demo 01";

      this.Width = 600;

      this.Height = 600;

 

      info = new Label();

      info.Text = "Blog Demo";

      info.Font = new System.Drawing.Font("Arial", 20);

      info.Dock = DockStyle.Top;

      info.AutoEllipsis = true;

      info.Height = 50;

 

      outerPanel = new Panel();

      outerPanel.BackColor = System.Drawing.Color.Green;

      outerPanel.MouseMove += new MouseEventHandler(outerPanel_MouseMove);

      outerPanel.Dock = DockStyle.Fill;

 

      innerPanel = new Panel();

      innerPanel.Width = 100;

      innerPanel.Height = 100;

      innerPanel.Left = 50;

      innerPanel.Top = 100;

      innerPanel.BackColor = System.Drawing.Color.Blue;

 

      this.Controls.Add(info);

      this.Controls.Add(outerPanel);

      this.outerPanel.Controls.Add(innerPanel);

    }

 

    void outerPanel_MouseMove(object sender, MouseEventArgs e)

    {

      System.Text.StringBuilder sb = new System.Text.StringBuilder();

      sb.Append("MouseMove at ");

      sb.Append(System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss:ffff tt"));

 

      info.Text = sb.ToString();

    }

 

    private Label info;

    private Panel outerPanel;

    private Panel innerPanel;

  }

}

 

In Avalon, MouseMove is a Routed Event. To be exact, it is a RoutedEvent with Bubble routing strategy. It means that MouseMove event moves up from source element to root element. In the previous demo, if RoutedEvent were used, event handler would still be called even if mouse is moving into innerPanel (Blue area). That is what you are seeing in the Avalon application below.

 

Create an Avalon application, edit Window1.xaml to be:

 

<Window x:Class="AvalonApplication1.Window1"

    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"

    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"

    Text="Zhanbo Blog Demo 01"

    Width="600"

    Height="600">

    <DockPanel>

        <TextBlock x:ID="info"

                   TextContent="Blog Demo"

                   FontFamily="Arial"

                   FontSize="20"

                   DockPanel.Dock="Top"/>

        <Canvas x:ID="outerCanvas"

                Width="200"

                Height="200" 

                Background="Green"

                MouseMove="CanvasMouseMove"

                DockPanel.Dock="Top"

                >

            <Canvas x:ID="innerCanvas" Width="100" Height="100"

                    Canvas.Top="50" Canvas.Left="50"

                    Background="Blue" />

        </Canvas>

    </DockPanel>

</Window>

 

In the code behind, add the event handler as follows:

void CanvasMouseMove(object sender, System.Windows.Input.MouseEventArgs e)

{

    System.Text.StringBuilder sb = new System.Text.StringBuilder();

    sb.Append("MouseMove at ");

    sb.Append(System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss:ffff tt"));
    info.TextContent = sb.ToString();

}

 

Please note that MouseEventArgs defined by WinForm and Avalon are under different namespaces and with different APIs.

 

By running this application, you can clearly see that outerCanvas receives MouseMove event when it occurs directly on itself and indirectly on its child control, which is innerCanvas. We can differentiate these two scenarios by checking MouseEventArgsSource property. Simply speaking, Source property refers to the object that raised the event. In the above demo application, Source can be either outerCanvas or innerCanvas. Let’s put it into use by changing the event handler in code behind:

 

void CanvasMouseMove(object sender, System.Windows.Input.MouseEventArgs e)

{

    System.Text.StringBuilder sb = new System.Text.StringBuilder();

    sb.Append("MouseMove at ");

sb.Append(System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss:ffff tt"));

    if (e.Source == innerCanvas)

    {

        sb.Append(" within inner canvas");

    }

    else

    {

        sb.Append(" within canvas");

    }

info.TextContent = sb.ToString();

}

 

I end this post with a question for my reader. If you want to reset info text back to initial string (Zhanbo Blog Demo 01) when the mouse left canvas area (i.e. when there is no MouseMove event to handle), I should handle a different event MouseLeave on outerCanvas. Let’s edit the XAML to add the attribute:

        <Canvas x:ID="outerCanvas"

                Width="200"

                Height="200" 

                Background="Green"

                MouseMove="CanvasMouseMove"

                MouseLeave="CanvasMouseLeave"

                DockPanel.Dock="Top"

                >

Now, what to add inside this new event handler? The shorter, the better. Of course, it has to be correct first.

void CanvasMouseLeave(object sender, MouseEventArgs e)

{

    //Question for my reader

}

 

(This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm)

Posted by zhanbos | 4 Comments
Filed under:

.NET Quiz (Q1-Q4)

A quick way to measure your understanding of .NET is to take some quiz questions. And from time to time, I will offer some for your enjoyment. The answers will be given out either in the comment and/or in the next Quiz blog. Of course, you can usually get the answers by testing the code yourself or checking out MSDN.

Q1: CAS (Code Access Security) is a/an ____ -based security system.
(1) Access (2) Code (3) Evidence (4) Permission (5) Principal

Q2: Class SuperList implements an indexer: public string this [int index]; When index is not within valid range, the indexer should throw:
(1) NotSupportedException (2) IndexOutOfRangeException
(3) InvalidOperationException (4)ArgumentOutOfRangeException

Q3: Which one complies with .NET design naming guideline:
(1)CLSCompliantAttribute (2)HttpSessionState.SessionID
(3)Color.FromArgb  (4)System.MarshalByRefObject

Q4: Which one works without an exception:
(1) object[] demo = new object[10];
     demo[10] = "demo10";
(2) System.Collections.ArrayList demo = new System.Collections.ArrayList(10);
     demo[9] = "demo9";
(3) System.Collections.Hashtable demo = new System.Collections.Hashtable(10);
    demo[8] = "demo8";

(This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm)

Posted by zhanbos | 14 Comments
Filed under:

MSN Newsbot, and more...

MSN Newsbot (beta) is an experimental, automated news service. It gathers news from over 4,000 sources on the internet and speed your discovery of news stories on the internet. News headlines are clustered together to allow you to compare coverage from multiple sources and each story links to the publisher's site where you can read the full article.

Try this and other technologies from MSN out at http://sandbox.msn.com/.

Posted by zhanbos | 7 Comments
Filed under:

Naming Issue: Callback vs CallBack

In Fx 1.x, we have both Context.DoCallBack method and AsyncCallback delegate. Which spelling is appropriate based on .NET Design Guideline?

The answer is Callback as “callback” is a single word. Going forward we should not see the spelling of CallBack in new Fx APIs.

Posted by zhanbos | 1 Comments
Filed under:

New Year 2004

I am back from month-long vacation, and my blog is officially moved from gotdotnet.com to msdn.com. In the New Year, I will continue my blog with the focus on .NET.

And it is almost Lunar New Year's Day today. If you celebrate it, Happy New Year of Monkey!

Posted by zhanbos | 3 Comments
Filed under:

Acronym Reference

The following acronyms are by no means official, but serve as reference when you see them. I will keep updating them. This blog is originally posted at gotdotnet.com.

B
BOL: Book On Line (Sql Server)

I
ICF: Internet Connection Firewall

L
LOB: Line Of Business

O
OOM: Out Of Memory (Exception)

R
ROI: Return On Investment
RS: Reporting Service

S
SO: Stack Overflow (Exception)
SOA: Service-Oriented Architecture (Application)

T
TA: Thread Abort (Exception)

W
WinFX: Windows Framework

(This posting is provided "AS IS" with no warranties, and confers no rights.)

Posted by zhanbos | 3 Comments
Filed under:
 
Page view tracker