This is a migrated thread and some comments may be shown as answers.

Problem with Silverlight DataGrid in a RadDropDownButton

4 Answers 69 Views
Buttons
This is a migrated thread and some comments may be shown as answers.
Tiklu
Top achievements
Rank 1
Tiklu asked on 08 Dec 2010, 08:28 AM
Hi All,
         I am adding one silverlight DataGrid inside the raddropdown button. The idea is we wanted to have a control which will give us a grid in the dropdown in place of a normal list in the dropdown. The problem is when we are adding items source to it and setting the items selection (do remember the dropdown is closed that is isOpen=false) and then when the user clicks on the dropdown button the contol is automatically firing a selection changed event for the DataGrid.  To solve this problem we tried assigning the selectionchanged event handler after setting up the itemsource..but still no use. That also does not worked.
           What we figured out is as soon as the selection is changed the grid is firing a selction changed event. But as the grid is in closed position the event is not getting properly executed. So as soon as somebody clicks on the open button the event is getting fired. Also inside the selectionchanged event handler we kept the code of closing the dropdown as IsOpen=false. But when it is happening for the first time. That is when after setting the selection of the grid for the first time through code when the user clicks the dropdownbutton then selectionchanged event handler is getting called and as a result the isopen=false is also getting executed. And it is giving a COM error. So we tried executing it through despatcher like this

this.Dispatcher.BeginInvoke(() =>
            {
                this.IsOpen = false;
            });

But as I told you the event handler is getting called for the first time automatically...so we ended up getting a flicker when the user clicks it for the first time. The dropdown opens and closes automically. Then again the user will have to click it and finally the dropdown opens.

So as I told you we need a dropdown control with a grid in it. If you think the control we are using is not correct one..do suggest us. Also If you think there are ways to fix this issue please do let us know. Also below is the whole source code of the control

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Telerik.Windows.Controls;
using System.Linq;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Data;
 
namespace Phoenix.Controlkit.Module
{
    [Description("This is a Custom Dropdown Control with a Grid")]
    public class PhoenixCustomDropdown : RadDropDownButton, IPhoenixClearAction
    {
        #region Private Members
        protected TextBlock lblContent;
        protected DataGrid grid;
        protected int itemCount;
        #endregion
 
        #region CTOR
        public PhoenixCustomDropdown()
            : base()
        {
            this.lblContent = new TextBlock();
            this.lblContent.Padding = new Thickness(10, 0, 0, 0);
            this.HorizontalContentAlignment = HorizontalAlignment.Left;
            this.Content = this.lblContent;
 
            //now the grid           
            SetupGrid();
 
            //this.Height = 25;
            this.itemCount = 0;
        }
 
        /// <summary>
        /// This method is actually setting up the grid
        /// </summary>
        protected void SetupGrid()
        {
            this.grid = new DataGrid();
            this.grid.HeadersVisibility = DataGridHeadersVisibility.Column;
            this.grid.IsReadOnly = true;
            this.grid.SelectionMode = DataGridSelectionMode.Single;
            this.grid.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
 
            //this.grid.SelectionChanged += grid_SelectionChanged;
            this.grid.LayoutUpdated += new EventHandler(grid_LayoutUpdated);
            this.grid.MouseLeftButtonDown += (o, e) =>
            {
                e.Handled = true;
            };
            this.grid.MouseLeftButtonUp += (o, e) =>
            {
                this.Dispatcher.BeginInvoke(() =>
                {
                    this.IsOpen = false;
                });
            };
 
            SolidColorBrush grdBack = new SolidColorBrush(Colors.Transparent);
            this.grid.RowBackground = grdBack;
            this.grid.AlternatingRowBackground = grdBack;
            this.DropDownContent = this.grid;
        }
 
        private void SetupBinding()
        {
            //for label
            var textBinding = new Binding(this.DisplayMemberPath);
            textBinding.Mode = BindingMode.TwoWay;
            this.lblContent.DataContext = this.grid.SelectedItem;
            this.lblContent.SetBinding(TextBlock.TextProperty, textBinding);
 
        }
        #endregion
 
        #region Event Handlers
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            switch (e.Key)
            {
                case Key.Down:
                    if (this.grid.SelectedIndex < this.itemCount) this.grid.SelectedIndex++;
                    break;
                case Key.Up:
                    if (this.grid.SelectedIndex > 0) this.grid.SelectedIndex--;
                    break;
            }
        }
 
        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            //this.Background = new SolidColorBrush(Colors.Orange);
            this.grid.Focus();
        }
        /// <summary>
        /// This is the event handler for grids selection change.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void grid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            SelectValues();
            this.Dispatcher.BeginInvoke(() =>
            {
                this.IsOpen = false;
            });
        }
 
        private void SelectValues()
        {
            this.lblContent.DataContext = this.grid.SelectedItem;
            this.SelectedItem = this.grid.SelectedItem;
            this.SelectedValue = GetMemberValue(this.SelectedItem);
        }
 
        /// <summary>
        /// for implementing the onevent pattern
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnSelectionChanged(System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (this.SelectionChanged != null)
            {
                this.SelectionChanged(this, e);
            }
        }
 
        void grid_LayoutUpdated(object sender, EventArgs e)
        {
            double width = 15;
            foreach (var col in this.grid.Columns)
            {
                width += col.ActualWidth;
            }
            this.grid.Width = (width > this.MinWidth) ? width : this.MinWidth;
            if (grid.ItemsSource != null)
            {
                double height = 12;
                foreach (var item in grid.ItemsSource)
                {
                    height += 25;
                    if (height > 160) break;
                }
                grid.Height = height;
            }
        }
 
        #endregion
 
        #region Properties
 
        #region Text
        /// <summary>
        /// The text property. It returns the text of the
        /// current combobox selection
        /// </summary>
        public string Text
        {
            get
            {
                return this.lblContent.Text;
            }
            set
            {
                this.lblContent.Text = value;
            }
        }
        #endregion
 
        #region GridHeight
        public static DependencyProperty GridHeightProperty =
          DependencyProperty.Register("GridHeight", typeof(double), typeof(PhoenixCustomDropdown), new PropertyMetadata(
              (o, e) =>
              {
                  ((PhoenixCustomDropdown)o).grid.Height = (double)e.NewValue;
              }
              ));
 
        /// <summary>
        /// The height of the grid
        /// </summary>
        public double GridHeight
        {
            get
            {
                return (double)GetValue(GridHeightProperty);
            }
            set
            {
                SetValue(GridHeightProperty, value);
            }
        }
        #endregion
 
        #region ItemsSource
        public static DependencyProperty ItemsSourceProperty =
          DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(PhoenixCustomDropdown), new PropertyMetadata(
              (o, e) =>
              {
                  if (e.NewValue == null) return;
                  PhoenixCustomDropdown cdd = (PhoenixCustomDropdown)o;
 
                  cdd.grid.SelectionChanged -= cdd.grid_SelectionChanged;
                  cdd.IsOpen = true;
                   
                  IList lst = (IList)e.NewValue;
                  cdd.itemCount = lst.Count;
                  if (cdd.DefaultValueText != null)
                  {
                      if (cdd.itemCount > 0)
                      {
                          var first = lst[0];
                          var blank = Activator.CreateInstance(first.GetType());
                          blank.GetType().GetProperty(cdd.DisplayMemberPath).SetValue(blank, cdd.DefaultValueText, null);
 
                          List<object> result = new List<object>();
                          result.Add(blank);
                          result.AddRange(lst);
                          cdd.grid.ItemsSource = result;
                          if (cdd.SelectedValue == null) cdd.SelectedValue = blank.GetType().GetProperty(cdd.ValueMemberPath).GetValue(blank, null);
                          cdd.SetupBinding();
                      }
                      else
                      {
                          cdd.Text = cdd.DefaultValueText;
                          cdd.SetupGrid(); //lets try resetting the grid once
                          cdd.SetupColumns(cdd.Columns);
                          cdd.grid.ItemsSource = lst;
                          cdd.grid.SelectedItem = null;
                      }
                  }
                  else
                  {
                      cdd.grid.ItemsSource = lst;
                      cdd.grid.SelectedItem = null;
                  }
                  if (cdd.DefaultDisplayColumn != null)
                  {
                      var deflt = (
                          from object itm in cdd.grid.ItemsSource
                          where (bool)itm.GetType().GetProperty(cdd.DefaultDisplayColumn).GetValue(itm, null)
                          select itm
                                   ).FirstOrDefault();
                      if (deflt != null)
                      {
                          if (cdd.SelectedValue == null) cdd.SelectedValue = deflt.GetType().GetProperty(cdd.ValueMemberPath).GetValue(deflt, null);
                      }
                  }
                  cdd.SetSelectionFromValue();
 
                  cdd.Dispatcher.BeginInvoke(() =>
                  {
                      cdd.IsOpen = false;
                      cdd.grid.SelectionChanged += cdd.grid_SelectionChanged;
                  });
              }
              ));
 
        /// <summary>
        /// The items source to be used for displaying the items in the grid
        /// </summary>
        public IEnumerable ItemsSource
        {
            get
            {
                return this.grid.ItemsSource;
            }
            set
            {
                SetValue(ItemsSourceProperty, value);
            }
        }
        #endregion
 
        #region SelectedItem
        public static DependencyProperty SelectedItemProperty =
          DependencyProperty.Register("SelectedItem", typeof(object), typeof(PhoenixCustomDropdown), new PropertyMetadata(
              (o, e) =>
              {
                  var cdd = (PhoenixCustomDropdown)o;
                  List<object> addeditems = new List<object>();
                  List<object> removeditems = new List<object>();
                  addeditems.Add(e.NewValue);
                  removeditems.Add(e.OldValue);
                  cdd.OnSelectionChanged(new System.Windows.Controls.SelectionChangedEventArgs(removeditems, addeditems));
              }
              ));
 
        /// <summary>
        /// This property gives the selected item
        /// </summary>
        [Description("This will return a single selected item from the items source")]
        public object SelectedItem
        {
            get
            {
                return ((object)(GetValue(PhoenixCustomDropdown.SelectedItemProperty)));
            }
            set
            {
                SetValue(PhoenixCustomDropdown.SelectedItemProperty, value);
            }
        }
        #endregion
 
        #region DisplayMemberPath
        public static DependencyProperty DisplayMemberPathProperty =
          DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(PhoenixCustomDropdown), null);
 
        /// <summary>
        /// Here you can set column name of the grid to be displayed in the selected textbox
        /// </summary>
        public string DisplayMemberPath
        {
            get
            {
                return ((string)(this.GetValue(PhoenixCustomDropdown.DisplayMemberPathProperty)));
            }
            set
            {
                this.SetValue(PhoenixCustomDropdown.DisplayMemberPathProperty, value);
            }
        }
        #endregion
 
        #region DefaultText
        /// <summary>
        /// This is the text which is going to come when there
        /// is no other values added to it.
        /// for example it will display none if you select nothing.
        /// </summary>
        [Description("Add the default value text which will be displayed by default in case there is no data selected")]
        public string DefaultValueText
        {
            get;
            set;
        }
        #endregion
 
        #region DefaultDisplayColumn
        [Description("Here you can add the default display column name which will be used for selecting the default value")]
        public string DefaultDisplayColumn
        {
            set;
            get;
        }
        #endregion
 
        #endregion
 
        #region copied code
 
        /// <summary>
        /// Returns the value from a particular record
        /// </summary>
        /// <param name="item">The single record to evaluate</param>
        /// <returns>The value memeber value</returns>
        private object GetMemberValue(object item)
        {
            return item.GetType().GetProperty(ValueMemberPath).GetValue(item, null);
        }
 
        private object GetMemberDisplay(object item)
        {
            try
            {
                return item.GetType().GetProperty(DisplayMemberPath).GetValue(item, null);
            }
            catch (Exception)
            {
                return null;
            }
        }
 
 
 
        public static DependencyProperty ValueMemberPathProperty =
          DependencyProperty.Register("ValueMemberPath", typeof(string), typeof(PhoenixCustomDropdown), null);
 
        /// <summary>
        /// The name of the colum whos value will be returned while a single item is selected
        /// </summary>
        public string ValueMemberPath
        {
            get
            {
                return ((string)(base.GetValue(PhoenixCustomDropdown.ValueMemberPathProperty)));
            }
            set
            {
                base.SetValue(PhoenixCustomDropdown.ValueMemberPathProperty, value);
            }
        }
 
        public static DependencyProperty SelectedValueProperty =
          DependencyProperty.Register("SelectedValue", typeof(object), typeof(PhoenixCustomDropdown),
          new PropertyMetadata((o, e) =>
          {
              //((PhoenixCustomDropdown)o).SetSelectionFromValue();
          }));
 
        /// <summary>
        /// The selected value,based on the selection
        /// </summary>
        public object SelectedValue
        {
            get
            {
                return GetValue(PhoenixCustomDropdown.SelectedValueProperty);
            }
            set
            {
                SetValue(PhoenixCustomDropdown.SelectedValueProperty, value);
            }
        }
 
        private void SetSelectionFromValue()
        {
            var value = SelectedValue;
            if (ItemsSource != null && value != null)
            {
                var sel = (from object item in this.grid.ItemsSource
                           where GetMemberValue(item).Equals(value)
                           select item).FirstOrDefault();
                if (sel == null)
                {
                    //try to get the blank value
                    sel = (from object itm in this.grid.ItemsSource where GetMemberDisplay(itm).Equals(this.DefaultValueText) select itm).FirstOrDefault();
                }
                if (sel != null)
                {
                    this.grid.SelectionChanged -= this.grid_SelectionChanged;
                    this.grid.SelectedItem = sel;
                    SelectValues();
                    this.grid.SelectionChanged += this.grid_SelectionChanged;
                }
            }
        }
        #endregion
 
        #region Public Methods
        [Description("Please do handle this event in case you want to do something specific while the selection of the combobox is changed")]
        public event System.Windows.Controls.SelectionChangedEventHandler SelectionChanged;
 
        protected string _cols;
        /// <summary>
        /// This is where we are going to pass the columns
        /// The format should be col1,col2,col3 etc.
        /// </summary>
        public string Columns
        {
            set
            {
                this._cols = value;
                SetupColumns(value);
            }
            get
            {
                return this._cols;
            }
        }
 
        /// <summary>
        /// Setup the grid
        /// </summary>
        /// <param name="value"></param>
        private void SetupColumns(string value)
        {
            this.grid.AutoGenerateColumns = false;
            this.grid.Columns.Clear();
 
            foreach (string col in value.Split(','))
            {
                string[] vals = col.Split('|');
                DataGridTextColumn dtcol = new DataGridTextColumn();
                dtcol.Binding = new System.Windows.Data.Binding(vals[0]);
                dtcol.Width = DataGridLength.SizeToCells;
                dtcol.Header = (vals.Length > 1 ? vals[1] : vals[0]);
                this.grid.Columns.Add(dtcol);
            }
            if (grid.Columns.Count == 1)
            {
                this.grid.HeadersVisibility = DataGridHeadersVisibility.None;
                this.grid.GridLinesVisibility = DataGridGridLinesVisibility.None;
            }
        }
        #endregion
 
        #region IPhoenixClearAction Members
        public void Reset()
        {
            var blank = (from object itm in this.ItemsSource where GetMemberDisplay(itm).Equals(this.DefaultValueText) select itm).FirstOrDefault();
            this.grid.SelectedItem = blank;
        }
 
        #endregion
    }
}

Hope this helps. Please do let me know if you have any question about any part of the code

Regards
Tiklu

4 Answers, 1 is accepted

Sort by
0
Petar Mladenov
Telerik team
answered on 11 Dec 2010, 11:57 AM
Hello Tiklu,

I am not sure whether I understand you correctly. You have achieved the behavior you want, but you don`t like the way you`ve done it ? By default, the SelectionChanged event fires after the DataGrid loads and there is at least one selected item. Could you  please elaborate more on  the reason you wish to prevent this initial firing ?
On the other hand, if you haven`t achieved the desired behavior, we would highly appreciate if you capture a video that shows the issue you have come up with. You can also send us an isolated sample and steps to reproduce it. Thank you for your cooperation in advance.

Greetings,
Petar Mladenov
the Telerik team
Browse the videos here>> to help you get started with RadControls for Silverlight
0
Tiklu
Top achievements
Rank 1
answered on 13 Dec 2010, 08:16 AM
Hi Peter,
          Thank you for your reply. You got it correct. I was been able to create the control just the way we wanted it. But as you said when the grid loads it also does an initial firing of selection changed. And as I have understood from your message that the grid gets loaded when the user clicks on the drop down button for the first time. So because of that when the user clicks on the button for the first time the dropdown is opening and the selection changed event is getting fired. 
void grid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            SelectValues();
            this.Dispatcher.BeginInvoke(() =>
            {
                this.IsOpen = false;
            });
        }

And the dropdown is getting closed all by itself for the first click. Which isn't really a very natural behavior. So we need to prevent the initial firing or atleast closing it initially. 

Hope this time I was clear. Thanks once more for your support.

Regards
Tiklu 
0
Tiklu
Top achievements
Rank 1
answered on 13 Dec 2010, 08:17 AM
Hi Peter,
          Thank you for your reply. You got it correct. I was been able to create the control just the way we wanted it. But as you said when the grid loads it also does an initial firing of selection changed. And as I have understood from your message that the grid gets loaded when the user clicks on the drop down button for the first time. So because of that when the user clicks on the button for the first time the dropdown is opening and the selection changed event is getting fired. 
void grid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            SelectValues();
            this.Dispatcher.BeginInvoke(() =>
            {
                this.IsOpen = false;
            });
        }

And the dropdown is getting closed all by itself for the first click. Which isn't really a very natural behavior. So we need to prevent the initial firing or atleast closing it initially. 

Hope this time I was clear. Thanks once more for your support.

Regards
Tiklu 
0
Tiklu
Top achievements
Rank 1
answered on 15 Dec 2010, 03:29 PM
Hi Peter,
            I was been able to fix the problem with flickering dropdown. Actually as you said that when the grid is getting loaded for the first time it will fire one selectionchanged event. So by using a flag variable and by setting it to true in the first load i was been able to fix the bug. That is in the load I have written
void grid_Loaded(object sender, RoutedEventArgs e)
       {
           this._isLoaded = true;  
       }

And I have changed the selection changed to
void grid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
       {
           SelectValues();
 
           if (this._isLoaded)
           {               
              this.IsOpen = false;
           }
       }

Regards
Tiklu
Tags
Buttons
Asked by
Tiklu
Top achievements
Rank 1
Answers by
Petar Mladenov
Telerik team
Tiklu
Top achievements
Rank 1
Share this question
or