Thursday, September 18, 2008

Silverlight Tip 8#: Measurement and layout pass in Silverlight run asynchronously!

If you try to use ActualWidth and ActualHeight properties, they sometimes are equal zero! Why? How fix it? You should read very good Tamir Khason's post about it.

Wednesday, September 17, 2008

Silverlight Tip 7#: Handling ListBox MouseLeftButtonDown event with VisualTreeHelper

In Silverlight 2 beta 2 ListBoxItem (and ListBox) doesn’t trigger MouseLeftButtonDown event anymore! You can add handler for this event in XAML of ItemContainerStyle. I wrote post about it 9 days ago.

If you wouldn't specify the event handler in XAML, you have a problem: you can't modify code of ListBoxItem class. But you can use visual tree after loading ListBox.

You can examine the visual tree structure with VisualTreeHelper class. I have written recursive method to get all visual elements which parents are objects of a specific type:

private static List<UIElement> children = new List<UIElement>();

...

private static void GetChildrenWithParentRec(UIElement parent, Type targetType)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
if (VisualTreeHelper.GetParent(child).GetType() == targetType)
{
children.Add(child);
}
GetChildrenWithParentRec(child, targetType);
}
}
}

I have used this method to obtain visuals which are children of ListBoxItem elements of ListBox control. Before you show ListBox, you can use code:

//GetChildrenWithParent uses GetChildrenWithParentRec
List<UIElement> children = ChildrenHelper.GetChildrenWithParent(listBox, typeof(ListBoxItem));

if (children.Count > 0){
foreach (UIElement el in children)
el.MouseLeftButtonDown += new MouseButtonEventHandler(ListBoxItem_MouseLeftButtonDown);
}

It works for me.

Friday, September 12, 2008

Silverlight Tip 6#: How to start editing cell of DataGrid by pressing text key

When you press text key (f.e 'a') on selected cell of MS Excel sheet, you automatically start write to the cell. Why don't implement this behavior with Silverlight DataGrid control? I have written some code to implement it:

void dataGrid_KeyDown(object sender, KeyEventArgs e)
{
//editing: bool flag which you set in BeginEdit, CancelEdit, CommitEdit event handlers
if (!editing)
{
//IsTextKey: custom function to check if pressed key is a text key (f.e letter, digit)
if (IsTextKey(e))
{
dataGrid.BeginEdit(e);
}
}
}


void dataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
if (e.Column.DisplayIndex == 0)
{
TextBox t = e.EditingElement as TextBox;

t.Focus();

KeyEventArgs args = e.EditingEventArgs as KeyEventArgs;

if (args != null)
{
if (IsTextKey(args))
{
//GetStrFromKey: custom function to get char from pressed key
//There isn't KeyPress event and KeyChar property of KeyPressEventArgs
t.Text = GetStrFromKey(args);
t.Select(t.Text.Length, t.Text.Length);
}
}
}
}

Thursday, September 11, 2008

Silverlight Tip 5#: DataGrid PreparingCellForEdit event

Recently I've used DataGridTemplateColumn in DataGrid control and I've implemented selecting text in cell before editing. This is standard behavior of predefined DataGridTextColumn, but using DataGridTemplateColumn you must implement it yourself (sample code below).

void dataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
if (e.Column.DisplayIndex == 0)
{
TextBox t = e.EditingElement as TextBox;

int length = t.Text.Length;

t.Focus();

KeyEventArgs args = e.EditingEventArgs as KeyEventArgs;

if (args != null)
{
if (args.Key == Key.F2)
t.Select(length, length);
}
else
{
t.Select(0, length);
}
}
}

Wednesday, September 10, 2008

Silverlight Tip 4#: How to detect user clicks outside of your control

In order to do this you may find application root and add mouse click event handler to it:


private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
...

//root
FrameworkElement root = Application.Current.RootVisual as FrameworkElement;
if (root != null)
{
root.MouseLeftButtonDown += new MouseButtonEventHandler(root_MouseLeftButtonDown);
}
}

and check when user clicks:


void root_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
double x = e.GetPosition(this).X;
double y = e.GetPosition(this).Y;

if (x < 0 || x > this.ActualWidth || y < 0 || y > this.ActualHeight)
{
//to do
}
}

But this is not ideal solution, because it doesn't work when you click outside your control and on the control which handles MouseLeftButtonDown event (for example Button). For more information see: http://silverlight.net/forums/p/23071/82288.aspx

Tuesday, September 9, 2008

Silverlight Tip 3#: ScrollIntoView

When you programmatically set selected item in a ListBox control and the item is unvisible, the Listbox doesn't automatically scroll to make it visible. But you can use ScrollIntoView method.


this.DropDownListBox.SelectedIndex = index;
this.DropDownListBox.UpdateLayout();
this.DropDownListBox.ScrollIntoView(this.DropDownListBox.SelectedItem);

Monday, September 8, 2008

Silverlight Tip 2#: How to implement a 'Hover-Selection' for the ListBox?

I'd like my control popup (see previous post) is similar to popup of combobox with hover-selection. There is no built-in ComboBox in Silverlight 2 Beta 2 and I've used ListBox control. But you can't select ListBoxItem by moving mouse on it.

Norbert Eder has written interesting post about how to implement it for the WPF ListBox. This solution has one bug - selection doesn't work when you move mouse outside DataTemplate area (outside the text of the ListBoxItem). I've resolved the problem by adding MouseMove event handler to ItemContainerStyle instead of DataTemplate.

Because MouseLeftButtonDown event doesn't fire for ListBox, I've added MouseLeftButtonDown event handler to ItemContainerStyle, too.


<Style x:Key="DropDownListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid MouseMove="Grid_MouseMove" MouseLeftButtonDown="Grid_MouseLeftButtonDown">
...

private void Grid_MouseMove(object sender, MouseEventArgs e)
{
Grid grd = sender as Grid;
this.DropDownListBox.SelectedItem = grd.DataContext;
}

Friday, September 5, 2008

Silverlight Tip 1#: How to find the absolute position of any control ?

For the past few weeks I've been working on Silverlight project, a prototype business application. So I'm going to write some posts about resolving practical problems with Silverlight 2.

I've written custom control with popup like combobox or listbox (see below).

<StackPanel x:Name="LayoutRoot" Height="Auto">
<TextBox x:Name="DropDownTextBox" />
<Popup x:Name="DropDownListPopUp">
<Grid>
<ListBox x:Name="DropDownListBox"
Style="{StaticResource DropDownListBoxStyle}"
ItemContainerStyle="{StaticResource DropDownListBoxItemStyle}"
ItemTemplate="{StaticResource DropDownDataTemplate}"
/>
</Grid>
</Popup>
</StackPanel>

How to display popup directly below the textbox? In order to do this you must get absolute position of the control on form:

GeneralTransform gt = this.TransformToVisual(Application.Current.RootVisual as UIElement);
Point offset = gt.Transform(new Point(0, 0));

and set VerticalOffset and HorizontalOffset properties of popup:

DropDownListPopUp.VerticalOffset = offset.Y + this.DropDownTextBox.ActualHeight;
DropDownListPopUp.HorizontalOffset = offset.X;

Finally, you display popup:

DropDownListPopUp.IsOpen = true;