Posts mit dem Label Best practice werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Best practice werden angezeigt. Alle Posts anzeigen

Samstag, 11. Dezember 2010

Windows Phone 7: Grid style listbox

If you want to implement a grid for your Windows Phone 7 app you have to use a Listbox control because of the missing DataGrid at the Windows Phone 7 platform. A first idea for contsructing a Grid then could be to use the WrapPanel control in your ItemTemplate of the Listbox control. But if you do this, the data virtualization of the ListBox will be disabled (Virtualizing Data in Windows Phone 7 Silverlight Applications - Peter Torr's Blog - Site Home - MSDN Blogs). This will increase the loading time of your ListBox and results in a very poor UX handling. The problem is, that you can not use any complex data structures in the ItemTemplate at a Listbox for Windows Phone 7. Every line in the Listbox should be one data line. So a solution for developing a grid styled Listbox is to implement a RowAdapter class for your bounded data.

Here a implementation of a RowAdapter, I used in one of my projects:

public class RowAdapter<TItemType> : IList<IEnumerable<TItemType>>, INotifyCollectionChanged
    {
        private readonly IList<TItemType> _sourceList;
        private readonly int _columns;
 
        private class RowObject : IEnumerable<TItemType>
        {
            internal readonly RowAdapter<TItemType> Parent;
            internal readonly int StartIndex;
 
            public RowObject(RowAdapter<TItemType> parent, int startIndex)
            {
                Parent = parent;
                StartIndex = startIndex;
            }
 
            #region IEnumerable<TItemType> Members
 
            public IEnumerator<TItemType> GetEnumerator()
            {
                int limit = Parent._sourceList.Count;
                int end = Math.Min(StartIndex + Parent._columns, limit);
 
                for (int pos = StartIndex; pos < end; ++pos)
                {
                    yield return Parent._sourceList[pos];
                }
            }
 
            #endregion
 
            #region IEnumerable Members
 
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
 
            #endregion
        }
 
        public RowAdapter(IList<TItemType> sourceList, int columns)
        {
            if (null == sourceList)
                throw new ArgumentNullException("sourceList", Resource.RowAdapter_RowAdapter_sourceList_is_null);
            if (columns <= 0)
                throw new ArgumentOutOfRangeException("columns", Resource.RowAdapter_RowAdapter_ColumnsGreaterOne);
 
            // We require the source list to implement IList because we
            // need to know how many item there are
            _sourceList = sourceList;
            _columns = columns;
 
            var sourceNotify = sourceList as INotifyCollectionChanged;
            if (null != sourceNotify)
            {
                sourceNotify.CollectionChanged += OnSourceCollectionChanged;
            }
        }
 
        #region IList<IEnumerable<TItemType>> Members
 
        public int IndexOf(IEnumerable<TItemType> item)
        {
            var realItem = item as RowObject;
            if (null == realItem || !ReferenceEquals(realItem.Parent, this))
                return -1;          // It does not belong to this collection
 
            Debug.Assert(0 == realItem.StartIndex % _columns, "RowObject item has a wierd index");
            return realItem.StartIndex / _columns;
        }
 
        public void Insert(int index, IEnumerable<TItemType> item)
        {
            throw new NotSupportedException();
        }
 
        public IEnumerable<TItemType> this[int index]
        {
            get
            {
                if (index < 0 || index > Count)
                    return null;
 
                return InternalGetRow(index);
            }
            set
            {
                throw new NotSupportedException();
            }
        }
 
        public void RemoveAt(int index)
        {
            throw new NotSupportedException();
        }
 
        #endregion
 
        #region ICollection<IEnumerable<TItemType>> Members
 
        public void Add(IEnumerable<TItemType> item)
        {
            throw new NotSupportedException();
        }
 
        public bool Contains(IEnumerable<TItemType> item)
        {
            var realItem = item as RowObject;
            return null != realItem && object.ReferenceEquals(realItem.Parent, this);
        }
 
        public void CopyTo(IEnumerable<TItemType>[] array, int arrayIndex)
        {
            // I haven't implemented this. It is easy to implement if you need it
            throw new NotImplementedException();
        }
 
        public bool Remove(IEnumerable<TItemType> item)
        {
            throw new NotSupportedException();
        }
        public void Clear()
        {
            throw new NotSupportedException();
        }
 
        public int Count
        {
            get
            {
                return (_sourceList.Count + (_columns - 1)) / _columns;
            }
        }
 
        public bool IsReadOnly
        {
            get { return true; }
        }
 
        #endregion
 
        #region IEnumerable<IEnumerable<TItemType>> Members
 
        public IEnumerator<IEnumerable<TItemType>> GetEnumerator()
        {
            for (int i = 0; i < Count; ++i)
            {
                yield return InternalGetRow(i);
            }
        }
 
        #endregion
 
        #region IEnumerable Members
 
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
 
        #endregion
 
        #region INotifyCollectionChanged Members
 
        public event NotifyCollectionChangedEventHandler CollectionChanged;
 
        private void FireCollectionChanged()
        {
            
            var handler = CollectionChanged;
            if (null != handler)
            {
                handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }
 
        private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            FireCollectionChanged();
        }
 
        #endregion
 
        private RowObject InternalGetRow(int index)
        {
           return new RowObject(this, index * _columns);
        }
    }

Samstag, 19. Juni 2010

Silverlight: Binding Events to ICommand interface (MVVM)

If you want to bind Silverlight controls which does not implement the ICommand interface to an ICommand method of your viewmodel, you can use System.Windows.Interactivity with the following pattern. In this sample I have implemented a SaveCommand at the LostFocus-Event of a TextBox.

XAML
   1: <TextBox 
   2:                                    Text="{Binding Name, Converter={StaticResource UpperCaseConverter}, Mode=TwoWay}" 
   3:                                    Grid.Column="0"                           
   4:                                    HorizontalAlignment="Stretch" 
   5:                                    VerticalAlignment="Center" 
   6:                                    Width="Auto" 
   7:                                    FontSize="18" 
   8:                                    Template="{StaticResource TextBoxFadeOut}">
   9:                                    <Interactivity:Interaction.Triggers>
  10:                                        <Interactivity:EventTrigger EventName="LostFocus" >
  11:                                            <Helpers:InvokeDelegateCommandAction 
  12:                                                Command="{Binding DataContext.SaveKategorieCommand, ElementName=LayoutRoot}" CommandParameter="{Binding}"/>
  13:                                        </Interactivity:EventTrigger>
  14:                                    </Interactivity:Interaction.Triggers>
  15:                                </TextBox>
Viewmodel-Command Definition
   1: public ICommand SaveKategorieCommand { get; set; }
   2:  
   3: SaveKategorieCommand = new DelegateCommand(SaveKategorie);
Viewmodel-Command Implementation

   1: private void SaveKategorie(object param)
   2:        {
   3:            if (param != null && param is AnzeigeKategorie)
   4:            {
   5:                var anz = param as AnzeigeKategorie;
   6:                if (anz.HasChanges)
   7:                {
   8:                    _domainContextSynchronizer.AddSubmitChangesToQueue();
   9:                }
  10:            }
  11:        }
Helper-Method InvokeDelegateCommandAction
   1: public sealed class InvokeDelegateCommandAction : TriggerAction<DependencyObject>
   2:     {
   3:         /// <summary>
   4:         ///
   5:         /// </summary>
   6:         public static readonly DependencyProperty CommandParameterProperty =
   7:             DependencyProperty.Register("CommandParameter", typeof(object), typeof(InvokeDelegateCommandAction), null);
   8:         
   9:         /// <summary>
  10:         ///
  11:         /// </summary>
  12:         public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
  13:             "Command", typeof(ICommand), typeof(InvokeDelegateCommandAction), null);
  14:  
  15:         /// <summary>
  16:         ///
  17:         /// </summary>
  18:         public static readonly DependencyProperty InvokeParameterProperty = DependencyProperty.Register(
  19:             "InvokeParameter", typeof(object), typeof(InvokeDelegateCommandAction), null);
  20:  
  21:         private string commandName;
  22:  
  23:         /// <summary>
  24:         ///
  25:         /// </summary>
  26:         public object InvokeParameter
  27:         {
  28:             get
  29:             {
  30:                 return this.GetValue(InvokeParameterProperty);
  31:             }
  32:             set
  33:             {
  34:                 this.SetValue(InvokeParameterProperty, value);
  35:             }
  36:         }
  37:  
  38:         /// <summary>
  39:         ///
  40:         /// </summary>
  41:         public ICommand Command
  42:         {
  43:             get
  44:             {
  45:                 return (ICommand)this.GetValue(CommandProperty);
  46:             }
  47:             set
  48:             {
  49:                 this.SetValue(CommandProperty, value);
  50:             }
  51:         }
  52:  
  53:         /// <summary>
  54:         ///
  55:         /// </summary>
  56:         public string CommandName
  57:         {
  58:             get
  59:             {
  60:                 return this.commandName;
  61:             }
  62:             set
  63:             {
  64:                 if (this.CommandName != value)
  65:                 {
  66:                     this.commandName = value;
  67:                 }
  68:             }
  69:         }
  70:  
  71:         /// <summary>
  72:         ///
  73:         /// </summary>
  74:         public object CommandParameter
  75:         {
  76:             get
  77:             {
  78:                 return this.GetValue(CommandParameterProperty);
  79:             }
  80:             set
  81:             {
  82:                 this.SetValue(CommandParameterProperty, value);
  83:             }
  84:         }
  85:  
  86:         /// <summary>
  87:         ///
  88:         /// </summary>
  89:         /// <param name="parameter"></param>
  90:         protected override void Invoke(object parameter)
  91:         {
  92:             this.InvokeParameter = parameter;
  93:  
  94:             if (this.AssociatedObject != null)
  95:             {
  96:                 ICommand command = this.ResolveCommand();
  97:                 if ((command != null) && command.CanExecute(this.CommandParameter))
  98:                 {
  99:                     command.Execute(this.CommandParameter);
 100:                 }
 101:             }
 102:         }
 103:  
 104:         private ICommand ResolveCommand()
 105:         {
 106:             ICommand command = null;
 107:             if (this.Command != null)
 108:             {
 109:                 return this.Command;
 110:             }
 111:             var frameworkElement = this.AssociatedObject as FrameworkElement;
 112:             if (frameworkElement != null)
 113:             {
 114:                 object dataContext = frameworkElement.DataContext;
 115:                 if (dataContext != null)
 116:                 {
 117:                     PropertyInfo commandPropertyInfo = dataContext
 118:                         .GetType()
 119:                         .GetProperties(BindingFlags.Public | BindingFlags.Instance)
 120:                         .FirstOrDefault(
 121:                             p =>
 122:                             typeof(ICommand).IsAssignableFrom(p.PropertyType) &&
 123:                             string.Equals(p.Name, this.CommandName, StringComparison.Ordinal)
 124:                         );
 125:  
 126:                     if (commandPropertyInfo != null)
 127:                     {
 128:                         command = (ICommand)commandPropertyInfo.GetValue(dataContext, null);
 129:                     }
 130:                 }
 131:             }
 132:             return command;
 133:         }
 134:     }

At this way you can bind each event of any Silverlight control to a ICommand.