Ivan Krivyakov's Blog

Premature optimization is the root of all evil

December 28, 2010

WPF Grid: Showing Tooltip over Empty Space

You can define a tooltip on a grid (or border), but it will show up only when the mouse hovers over space “occupied” by a grid item. Empty space will not generate the tooltip. E.g.

<Grid ToolTip="Yo!">
   <Rectangle Fill="Yellow" Width="50" Height="50" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>

The tooltip will show only when hovering over a small yellow rectangle in the middle, and not elsewhere in the grid. If you add <Rectangle /> to the grid, it won’t help. The rectangle by default gets null fill brush and sort of “does not count”. If you want to extend the tooltip on the whole grid, do this:

<Grid ToolTip="Yo!">
   <Rectangle Fill="Transparent" />
   ... other items ...
</Grid>

This will ensure the tooltip is visible everywhere in the grid. However, if the grid is large, this looks somewhat weird. The tooltip appears once and does not follow the mouse. Thus, for large grids it may be better to leave things as they are.

The same technique applies to a StackPanel and WrapPanel. Only space actually covered by controls like Label, Button, or Rectangle will have tooltip. Hovering over empty space will not yield a tooltip.

WPF Custom Panels: Thou Shalt Measure Your Children

Custom panels must call Measure() on their children, even if they ignore the result. Calling just Arrange() causes weird behavior: wrap panels don’t wrap, controls get out of bounds, etc. This is probably documented somewhere, but I found it the hard way. This is a little unexpected: if I know exactly how much space I want to give the child, why should I ask it how much it wants? Well, it turns out I must still ask, or else.

December 23, 2010

If your custom WPF control just would not show up

Story in a nutshell – my custom control was not visible. Reason: we defined default style for the control in our own resource dictionary, which was merged into application resources. It worked fine until we wanted to redefine the style locally in one of the controls.

<!-- default style -->
<Style TargetType="{x:Type MyControl}">
   <Setter Property="Tempalte">
       <Setter.Value>
           <ControlTempalte>...</ControlTemplate>
       </Setter.Value>
   </Setter>
</Style>

<MyControl>
   <!-- local style -->
   <Style TargetType={x:Type MyControl}">
       <Setter Property="Background" Value="Blue" />
   </Style>
</MyControl>

If your default style is defined in generic.xaml, WPF will merge local style with default style. If your default style is defined elsewhere, local style will completely override the default style. In this case, if local style does not define a control template, your control will end up without a template and thus will not be rendered. That is, you can snoop it, smell it, get its coordinates, make sure IsVisible is true, but it won’t show up.

Possible fixes are (in order of cleanness):
- move default style to generic.xaml
- remove local style so default style is not overridden
- add control template definition to the local style

Why I don’t like SQL

Had a discussion with some friends where I said I don’t like SQL and consider it an atavism. They disagreed and said that SQL is a very insightful language and is much cooler that, say, Javascript. I probably need to summarize why I don’t like SQL, so at least I myself understand the source of my dissatisfaction.

December 16, 2010

.NET: decimal is not primitive?

typeof(decimal).IsPrimitive returns false. Apparently, this is a feature. Deep down, in the IL world, decimal is just a struct, and the fact that it has a keyword in C# does not change it.

December 2, 2010

WPF: Passing Data to Sub-Views via DataContext Causes Trouble

Imagine I have a main view that contains a number to sub-views. To be concrete, let’s assume that the sub-view is a user control of type PersonView that displays person’s information.

How do you tell the PersonView what person to display? Passing it via DataContext seems like a natural choice, so the XAML looks something liek this:

<StackPanel ...>
   <my:PersonView DataContext={Binding PersonA} />  
   <my:PersonView DataContext={Binding PersonB} />
</StackPanel>

This is all fine until I try to set other properties of PersonView, e.g.

<my:PersonView Visibility={Binding IsPersonAVisible} DataContext={Binding PersonA} />

The IsPersonAVisible binding does not work. The system first binds the DataContext property to the PersonA object, and then all other bindings are processed against PersonA. Since PersonA does not have "IsPersonAVisible", the binding fails.

Microsoft representatives confirm this and provide some explanation why it should be so, but personally I think it does not sound very convincing.

We can make the binding work by diverting it away from the redirected DataContext, e.g. by specifying ElementName:

<my:PersonView 
    Visibility={Binding DataContext.IsPersonAVisible, ElementName=MainWindow}
    DataContext={Binding PersonA} />

but it looks very ugly and long.

We have two ways to avoid this ugliness:
- move the properties into the PersonA object
- use property other than DataContext to pass person information.

The former does not really solve the problem. Even if we have PersonA.IsVisible, it most likely will depend on some external data, and we will need to write code to set it duplicating the work the binding would do for us.

The latter wrecks havoc in the bindings of the PersonView itself, but this can be rectified. When we passed person information via DataContext, the PersonView looked probably something like this:

<UserControl ...>
    <StackPanel>
        <TextBlock Text="{Binding FirstName}" />
        <TextBlock Text="{Binding LastName}" FontWeight="Bold" />
    </StackPanel>
</UserControl>

If we now pass person information in control property like Person, all bindings will stop working. We can fix it by setting DataContext on the main visual, so it can done only once:

<UserControl ...>
    <StackPanel DataContext="{Binding Person, RelativeSource={RelativeSource AncestorType=UserControl}}">
        <TextBlock Text="{Binding FirstName}" />
        <TextBlock Text="{Binding LastName}" FontWeight="Bold" />
    </StackPanel>
</UserControl>

Now, things are back to normal, and we have only one ugly binding. Note, that setting data context on the user control itself won't help, because it will lead to the same problem as before.