ComboBox in DataGrid Issue with RC0

we will run into wierd issue, if we try to use ComboBox in DataGrid (Silverlight RC0). you can find more details in this forum post

I think this will be addressed before Silverlight 2.0 is released. Here is a temporary workaround

I  got all the code from the forum post, ( so I am not including the code)

Here is a workaround

Create a class, similar to this and set IsDropDownProperty to true in the loaded event

 public class MyComboBox : ComboBox
    {
        public MyComboBox()
        {
            DefaultStyleKey = typeof(ComboBox);
            this.Loaded += new RoutedEventHandler(MyComboBox_Loaded);   
        }       
      void MyComboBox_Loaded(object sender, RoutedEventArgs e)
        {
            IsDropDownOpen = true;
        }
      
    }
After declaring the Namespace
xmlns:src=”clr-namespace:xxx;assembly=xxx”

 use the combobox like this

<data:DataGridTemplateColumn Header=”City”>
  <data:DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
      <TextBlock Text=”{Binding City}” />
    </DataTemplate>
  </data:DataGridTemplateColumn.CellTemplate>
  <data:DataGridTemplateColumn.CellEditingTemplate>
    <DataTemplate>
      <src:MyComboBox SelectedItem=”{Binding City, Mode=TwoWay}”
            ItemsSource=”{Binding CityList, Source={StaticResource cityProvider}}”
                                  />
    </DataTemplate>
  </data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
 
 

 

Advertisements

Adding mouse wheel support to ScrollViewer

As we know there is no MouseWheel support to scroll as of Beta 2 and there are many ways out there to do the same

here is another way to do the same

Lets start with a couple of ListBoxes one which we add MouseWheel support and other not

  <Canvas>
        <ListBox x:Name=”list1″
                 Width=”200″
                 Height=”200″
                 local:Page.AddMouseWheelSupport=”true”></ListBox>     
        <ListBox x:Name=”list3″
                 Margin=”250, 0,0,0″
                 Width=”200″
                 Height=”200″></ListBox>
    </Canvas>

you will see a datagrid(in the samplecode). I thought it uses ScrollViewer. looks like it doesn’t so this technique wont work with that. This should work with any control that has ScrollViewer, did not try for any other Control.

Created a Attached property like this

public static bool GetAddMouseWheelSupport(DependencyObject obj)
        {
            return (bool)obj.GetValue(AddMouseWheelSupportProperty);
        }

        public static void SetAddMouseWheelSupport(DependencyObject obj, bool value)
        {
            obj.SetValue(AddMouseWheelSupportProperty, value);
        }

        // Using a DependencyProperty as the backing store for AddMouseWheelSupport.  This enables animation, styling, binding, etc…
        public static readonly DependencyProperty AddMouseWheelSupportProperty =
            DependencyProperty.RegisterAttached(“AddMouseWheelSupport”, typeof(bool), typeof(Page), new PropertyMetadata(ValueChanged));

        static void ValueChanged(DependencyObject obj,DependencyPropertyChangedEventArgs args)
        {
          
        }

you can see that we added the attached property to the 1st listbox

local:Page.AddMouseWheelSupport=”true”

we did not add this attribute to the 2nd listbox, so one of them will have the support and other wont

The main logic is in the function OnMouseWheel.1st part of the code is copied from Mike snow’s Blog, along with code for hooking up the events

 HtmlPage.Window.AttachEvent(“DOMMouseScroll”, OnMouseWheel);
 HtmlPage.Window.AttachEvent(“onmousewheel”, OnMouseWheel);
 HtmlPage.Document.AttachEvent(“onmousewheel”, OnMouseWheel);

 private void OnMouseWheel(object sender, HtmlEventArgs args){   
            double mouseDelta = 0;   
            ScriptObject e = args.EventObject;     // Mozilla and Safari   
            if (e.GetProperty(“detail”) != null)     {       
                mouseDelta = ((double)e.GetProperty(“detail”));   
            }   
            // IE and Opera   
            else if (e.GetProperty(“wheelDelta”) != null)        
                mouseDelta = ((double)e.GetProperty(“wheelDelta”));    

            List<UIElement> elements = this.HitTest(new Point(args.ClientX, args.ClientY)) as List<UIElement>;
            Control control = null;
            for (int i = 0; i < elements.Count-1; i++)
            {

                if (elements[i].GetType() == typeof(ListBox))
                {
                    control = elements[i] as Control;
                    break;
                }
             
            }

            if (control == null)
                return;
           
            mouseDelta = Math.Sign(mouseDelta);
            if ((bool)control.GetValue(AddMouseWheelSupportProperty))
            {
                ScrollViewer sv = GetChild<ScrollViewer>(control);
                if (sv == null)
                    return;
                if (mouseDelta == 1)
                    sv.ScrollToVerticalOffset(sv.VerticalOffset – 30);
                else
                    sv.ScrollToVerticalOffset(sv.VerticalOffset + 30);
            }
        }

What we are doing is doing a HitTest with ClientX and ClientY positions  to get a list of elements and checking is there is a listbox in the list of elements returned.
If we find one and the AttachedProperty ‘AddMouseWheelSupportProperty’ is set we get the ScrollViewer from the VisualTree and change the verticaloffset by some amount (30 in the sample)

you can download the code here

Getting the control inside a DataTemplate

Here is one way we can get to the Control inside a DataTemplate

lets start with the following XAML in the page

<UserControl x:Class=”SilverlightApplication1.Page”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
    xmlns:local=”clr-namespace:SilverlightApplication1;assembly=SilverlightApplication1″
    Width=”400″ Height=”300″>
    <UserControl.Resources>
        <DataTemplate x:Key=”dt”>
            <local:Product Margin=”2″/>
        </DataTemplate>
    </UserControl.Resources>
    <Canvas>
        <StackPanel>
            <ListBox Height=”300″  x:Name=”list1″ Margin=”100,100,100,20″ ItemTemplate=”{StaticResource dt}”/>        
            <Button Content=”change background” Width=”150″ Click=”Button_Click”></Button>
        </StackPanel>
    </Canvas>
</UserControl>

So, we have a listbox with an ItemTemplate with  a UserControl in it, by default we are going to get a StackPanel for the ItemsPanel.

we are going to bind the listbox to some data
 void Page_Loaded(object sender, RoutedEventArgs e)
        {
            List<ProductData> products = new List<ProductData>();
            for (int i = 0; i < 20; i++)
                products.Add(new ProductData { ProductID = i, Name = “Product..” + i.ToString() });

            list1.ItemsSource = products;
        }

The product class is defined like this

 public class ProductData
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
    }

when we click the button we will change the background of the StackPanel which is in the UserControl(Product)

UserControl(Product) is defined like this

<UserControl x:Class=”SilverlightApplication1.Product”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml” 
    >
    <StackPanel x:Name=”stackpanel1″ Orientation=”Horizontal”>
        <TextBlock Text=”{Binding ProductID}”></TextBlock>
        <TextBlock Margin=”10,2,2,2″ Text=”{Binding Name}”></TextBlock>
    </StackPanel>
</UserControl>

The Method to get the child of a given type from the VisualTree is below

public T GetChild<T>(DependencyObject obj) where T : DependencyObject
        {
            DependencyObject child = null;
            for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child.GetType() == typeof(T))
                {
                    break;
                }
                else if (child != null)
                {
                    child = GetChild<T>(child);
                    if (child != null && child.GetType() == typeof(T))
                    {
                        break;
                    }
                }
            }
            return child as T;
        }
The Click event handler for the button is defined like this

 private void Button_Click(object sender, RoutedEventArgs e)
        {
            ListBoxItem lbi;           
            StackPanel sp = GetChild<StackPanel>(list1) as StackPanel;         
            for (int i = 0; i < sp.Children.Count; i++)
            {
                lbi = sp.Children[i] as ListBoxItem;
                Product p = GetChild<Product>(lbi);
                p.SetBGColor();
            }
        }

1. we are getting the 1st StackPanel in the VisualTree of the ListBox, this holds all the items of the listbox
2. As the content of the Datatemplate gets wrapped in a ListBoxItem as get that
3. finally we go to the actual Control, in our case the product using the helper function we wrote. once we get the control I am calling a method in that to change the Background color

you can download the code here

DataGrid sample with SelectAll Checkbox

Here is a one way of getting the SelectAll functionality in DataGrid

the DataGrid is defined like this

 <my:DataGrid x:Name=”myDataGrid”
                     VerticalAlignment=”Top”
                     Width=”350″
                     Height=”300″
                     Grid.Column=”0″
                     AutoGenerateColumns=”False”>
            <my:DataGrid.Columns>
                <my:DataGridTemplateColumn Width=”80″>
                    <my:DataGridTemplateColumn.Header>
                        <CheckBox HorizontalAlignment=”Center”
                                  Click=”chk_Click”
                                  VerticalAlignment=”Center”
                                  x:Name=”chkAll”></CheckBox>

                    </my:DataGridTemplateColumn.Header>
                    <my:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <CheckBox x:Name=”chk”
                                      HorizontalAlignment=”Center”
                                      HorizontalContentAlignment=”Center”></CheckBox>
                        </DataTemplate>
                    </my:DataGridTemplateColumn.CellTemplate>

                </my:DataGridTemplateColumn>
                <my:DataGridTextColumn Header=”First Name”
                                       Width=”100″
                                       DisplayMemberBinding=”{Binding FirstName}”
                                       FontSize=”20″ />
                <my:DataGridTextColumn Header=”Last Name”
                                       Width=”100″
                                       DisplayMemberBinding=”{Binding LastName}”
                                       FontSize=”20″ />
            </my:DataGrid.Columns>
        </my:DataGrid>

when the rows are being loaded we keep a track of all the checkboxes in the LoadingRow eventhandler

 void myDataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            CheckBox chk = myDataGrid.Columns[0].GetCellContent(e.Row) as CheckBox;
            chk.IsChecked = false;
            checkboxes.Add(chk);
        }

and when the checkbox in the header is clicked we check/uncheck each of the items in the list of checkboxes.

 void chk_Click(object sender, RoutedEventArgs e)
        {
            CheckBox chk = sender as CheckBox;
            bool check   = chk.IsChecked.Value;
            foreach (CheckBox chkbox in checkboxes)
                chkbox.IsChecked = check;          
        }

you can download the code here

Note:
This will work only if datagrid has no scrolling.
I will post couple more, one that will work with scrolling and other changing the class to add a property so we can use databinding.