Wednesday, April 18, 2007

TreeView and ObservableCollection weird bug

Recently my client points me to strange bug, if observable collection is binded to treeview and you are trying to perform Move or Replace action on the ObservableCollection, the exception "Unexpected collection change action 'Replace'" or "Unexpected collection change action 'Move'" will be thrown. This not happens with other ItemsControl e.g. ListBox. So, this is one of WPF confirmed bugs (which actually already fixed). How to build workaround? Simple, you do not have to know WPF in order to do it. Just replace internal method MoveItem with, e.g. InsertItem and RemoveItem and that'll work fine. BTW, you can not do straight assignment (replace), 'cos this will throw the same exception. So, please see proposed workaround for this problem.

    public class FixedObservableCollection<T> : ObservableCollection<T>
    {
        protected override void MoveItem(int oldIndex, int newIndex)
        {
            //base.MoveItem(oldIndex, newIndex);
            T oItem = base[oldIndex];
            base.InsertItem(oldIndex, base[newIndex]);
            base.RemoveAt(oldIndex + 1);
 
            base.InsertItem(newIndex, oItem);
            base.RemoveAt(newIndex + 1);
 
        }    
    }

Tuesday, April 17, 2007

ListBoxItem index or enumirating of ListBox Items over filtered data source

One of my clients asked me today, how it possible to put the index of ListBox Item near the value, while data source of it might be filtered. How to get the index from inside data source all of you, I believe, knows, but how to ask out host (it might be any ItemsCountrol) about what the real index of the binded item, while some of items are hidden.

So, in order to do it, we have to be able to "ask" items control about the real position of it's items. How to do it? Simple, ItemsControl has really useful static method, named ItemsControlFromItemsContainer and the control's ItemContainerGenerator has method named IndexFromContainer. This means, that if we have an item and the item control, we can figure it's index. But first of all, how to get into Item, while using templates? I explained it earlier when spoke about Autoexplainable listbox items. You should use FindAncestor type of Relative binding to get an ancestor of the template.

 

<DataTemplate x:Key="myItem">
        <TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}"/>
</DataTemplate>

Now, let's think a bit, who can add not data relevant information to our DataTemplate? Who invoked each time the item do something with binding? The right answer is converter. Let's create it.


 



class MyConverter:IValueConverter
    {
        #region IValueConverter Members
 
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            ListBoxItem item = value as ListBoxItem;
            ListBox view = ItemsControl.ItemsControlFromItemContainer(item) as ListBox;
            int index = view.ItemContainerGenerator.IndexFromContainer(item);
 
 
            return index.ToString()+" - "+item.Content.ToString();
        }

So, we done, the only thing I want to explain here is how to filter data for those, who do not know. ICollectionView - the interface, that implements the collection has Filter property, that receives strange class named Predicate<object>. This one will do your work. So let's first create a simple strings collection and set it as data context for our application


 



List<string> numbers = new List<string>();
            for (int i = 0; i < 10; i++)
            {
                numbers.Add("Item " + i);
            }
            myPanel.DataContext = numbers;

Next let's add filter property while check box (see screenshot) is checked. Steps? Get collection and filter it :)


 



void onChecked(object sender, RoutedEventArgs e)
        {
            ICollectionView view = CollectionViewSource.GetDefaultView(myPanel.DataContext) as ListCollectionView;
 
            view.Filter = new Predicate<object>(FilterOdds);
        }

On filter let's remove all odd numbers from our collection. Like this:


 



bool FilterOdds(object item)
        {
            return int.Parse(item.ToString().Remove(0, 5)) % 2 == 1;
        }

Done, remove the filter on Unchecked event and we done


 



void onUnchecked(object sender, RoutedEventArgs e)
        {
            ICollectionView view = CollectionViewSource.GetDefaultView(myPanel.DataContext) as ListCollectionView;
 
            view.Filter = null;
        }

That's all, folks.


Source code for this article

Monday, April 16, 2007

Now it's official - WPF/E=SilverLight

New "flash killer" is here and this time it's serious. Today at NAB conference, Microsoft announced and demonstrates it's next generation cross-platform web technology Microsoft Silverlight. Actually, it's totaly rebranded WPF/E . Microsoft Silverlight will enable content providers to deliver media experiences and rich interactive applications that incorporate media, graphics, animation, and much, much more with full application functionality on both Windows and Mac platforms and inside IE, Firefox and Safari. Silverlight users will also enjoy compatibility with the broad ecosystem of Windows Media (VC-1) enabled tools and solutions, including existing and upcoming IIS and Windows Streaming Media server technologies.

So it comes with new server side products - Expression Media Encoder, IIS7 Media Pack and broadcast system. You can learn more about this new technology here.

Also, there are some interesting comparison tables between WPF, SilverLight and Flash at MED blog as well as at off. site of this platform.

 

Generic Grid with column autodetection

There are a lot of questions such as "how can I generate columns in my grid, based on my XML data?", how to implement generic sort algorithm?" etc. In this post I'll try to explain how to use ListView with GridView, how to sort your data presentation, without sorting data source and lost binding. How to parse generic Excel or CSV data and put it into grid. 

So, first thing, you should do is to create generic ListView with Grid as view. We'll put it into UserControl in order us to be able to use it externally. This part is really simple.

 

<ListView Name="myListView" >
        <ListView.View>
          <GridView x:Name="myGridView"/>
        </ListView.View>
</ListView>

Now, we'll create dependency property for our data source. We'll have to handle the assignment of data source in order to great columns dynamically within the GridView


 



public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MyGrid),
            new PropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));
 
        static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null)
                return;
 
            MyGrid grid = d as MyGrid;
 
            grid.myListView.SetValue(ItemsControl.ItemsSourceProperty, e.NewValue);
 
            ReadOnlyObservableCollection<XmlNode> col = e.NewValue as ReadOnlyObservableCollection<XmlNode>;
            if (col != null)
            {
                grid.myGridView.Columns.Clear();
                if (col.Count > 0)
                {
                    foreach (XmlNode node in col[0].ChildNodes)
                    {
                        bindGridViewColumn(node, grid.myGridView);
                    }
                    foreach (XmlAttribute attr in col[0].Attributes)
                    {
                        bindGridViewColumn(attr, grid.myGridView);
                    }
                }
                else
                {
                    throw new NotSupportedException("No description row found in data provided. You should have at least one row to populate control columns");
                }
            }
            else
            {
                throw new NotImplementedException("This patch is working only for XmlSource by now");
            }
 
        }

So, what are we doing here? First of all, if we have null or unsupported data as datasource we'll do nothing. Then, if the data is valid, we'll clear all old columns and create new set, based on data passed. We'll look into all child nodes of DocumentRoot in order to build markup. The next step is to bind each column data of our data source to the column and then bind all data to datasource of the grid. We'll do it either for elements and attributes of our XML. Here is comes


 



static internal void bindGridViewColumn(XmlNode node, GridView view)
        {
            bindGridViewColumn(node.Name, view);
        }
static internal void bindGridViewColumn(XmlAttribute attr, GridView view)
        {
            bindGridViewColumn("@"+attr.Name, view);
        }
static void bindGridViewColumn(string XPath, GridView view)
        {
            GridViewColumn gvc = new GridViewColumn();
            gvc.Header = XPath[0]=='@'?XPath.Substring(1):XPath;
            Binding b = new Binding();
            b.XPath = XPath;
            gvc.DisplayMemberBinding = b;
            view.Columns.Add(gvc);
        }

Simple setter and getter for DP and we done.


 



public IEnumerable ItemsSource
        {
            get
            {
                GetValue(ItemsSourceProperty);
                return (IEnumerable)myGridView.GetValue(ItemsControl.ItemsSourceProperty);
            }
            set
            {
                SetValue(ItemsSourceProperty, value);
                myGridView.SetValue(ItemsControl.ItemsSourceProperty, value);
            }
        }

Now, let's take care on data sorting. We'll handle click on grig view column header as trigger for sort so, GridViewColumnHeader.Click="onSort". Now, let's handle it. The code is rather straight forward, so I'll now going to explain it


 



GridViewColumnHeader m_lastHeaderClicked = null;
        ListSortDirection m_lastDirection = ListSortDirection.Ascending;
 
        void onSort(object sender, RoutedEventArgs e)
        {
            GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
            ListSortDirection direction;
 
            if (headerClicked != null)
            {
                if (headerClicked.Role != GridViewColumnHeaderRole.Padding)
                {
                    if (headerClicked != m_lastHeaderClicked)
                    {
                        direction = ListSortDirection.Ascending;
                    }
                    else
                    {
                        if (m_lastDirection == ListSortDirection.Ascending)
                        {
                            direction = ListSortDirection.Descending;
                        }
                        else
                        {
                            direction = ListSortDirection.Ascending;
                        }
                    }
 
                    string header = headerClicked.Column.Header as string;
                    Sort(header, direction);
 
                    if (direction == ListSortDirection.Ascending)
                    {
                        headerClicked.Column.HeaderTemplate =
                          Resources["HeaderTemplateArrowUp"] as DataTemplate;
                    }
                    else
                    {
                        headerClicked.Column.HeaderTemplate =
                          Resources["HeaderTemplateArrowDown"] as DataTemplate;
                    }
 
                    m_lastHeaderClicked = headerClicked;
                    m_lastDirection = direction;
                }
            }
        }
 
void Sort(string sortBy, ListSortDirection direction)
        {
            ICollectionView dataView = CollectionViewSource.GetDefaultView(myListView.ItemsSource);
 
            dataView.SortDescriptions.Clear();
            SortDescription sd = new SortDescription(sortBy, direction);
            dataView.SortDescriptions.Add(sd);
 
            dataView.Refresh();
        }

That's it, we done. The next step is to create control to handle drag and drop of Excel data and convert it into XML data source for our smart grid view. We'll inherit from Canvas control and implement INotifyPropertyChanged in order to notify about changes in datasource and providing an ability to bind data between controls. So, first of all, let's create a couple of DPs to provide interface for dropped object and dropped data.


 



    

    class ExcelDataReader:Canvas,INotifyPropertyChanged
    {
        public ExcelDataReader():base()
        {
            this.AllowDrop = true;
        }
 
        
public static DependencyPropertyKey DroppedObjectPropertyKey = 
            DependencyProperty.RegisterReadOnly("DroppedObject", 
            typeof(ReadOnlyObservableCollection<XmlNode>), 
            typeof(ExcelDataReader),
            new PropertyMetadata(null));
 
 
        public static readonly DependencyProperty DroppedObjectProperty = DroppedObjectPropertyKey.DependencyProperty;
 
        public ReadOnlyObservableCollection<XmlNode> DroppedObject
        {
            get { return (ReadOnlyObservableCollection<XmlNode>)GetValue(DroppedObjectProperty); }
        }
 
        
public static DependencyPropertyKey DroppedDocumentPropertyKey =
    DependencyProperty.RegisterReadOnly("DroppedDocument",
    typeof(XmlDocument),
    typeof(ExcelDataReader),
    new PropertyMetadata(null));
 
        public static readonly DependencyProperty DroppedDocumentProperty = DroppedDocumentPropertyKey.DependencyProperty;
 
        public XmlDocument DroppedDocument
        {
            get { return (XmlDocument)GetValue(DroppedDocumentProperty); }
        }

The next step is to handle preview of drop event. Why preview? In order to leave future developers to add custom logic to drop event of this control


 



protected override void OnPreviewDrop(System.Windows.DragEventArgs e)
        {  
 
            if (e.Data.GetDataPresent(DataFormats.CommaSeparatedValue))
            {
                List<string> strs = new List<string>();
                using (StreamReader sr = new StreamReader((Stream)e.Data.GetData(DataFormats.CommaSeparatedValue)))
                {
                    while (sr.Peek() > 0)
                    {
                        strs.Add(sr.ReadLine());
                    }
                }
 
                SetDroppedObject(strs);
                base.OnDrop(e);
 
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
 
          base.OnPreviewDrop(e);
 
 
        }

Now the logic. Once I got something I can handle dropped, I'll convert it into well known XML representation in order to be able to provide it as data source for our previous control.


 



        private void SetDroppedObject( List<string> data)
        {
 
            ObservableCollection<XmlNode> nodes = new ObservableCollection<XmlNode>();
            XmlDocument doc = new XmlDocument();
            doc.LoadXml("<root></root>");
            bool fData = MessageBox.Show("Treat first row as title?", "Data dropped", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
            if (fData && data.Count < 1)
            {
                throw new NotSupportedException("No description row found in data provided. You should have at least one row to populate control columns");
            }
            int startRow = fData ? 1 : 0;
 
            string[] titles = data[0].Split(',');
 
            for (int i = startRow; i < data.Count; i++)
            {
                XmlElement elem = doc.CreateElement("node");
                string[] items = data[i].Split(',');
 
                for (int j = 0; j < items.Length; j++)
                {
                    string title = fData ? titles[j].Trim() : "data" + j.ToString();
                    XmlElement el = doc.CreateElement(title);
                    XmlText txt = doc.CreateTextNode(items[j]);
                    el.AppendChild(txt);
                    elem.AppendChild(el);
                }
                doc.DocumentElement.AppendChild(elem);
                nodes.Add(elem);
            }
 
            this.SetValue(DroppedDocumentPropertyKey, doc);
            this.SetValue(DroppedObjectPropertyKey, new ReadOnlyObservableCollection<XmlNode>(nodes));
 
            if (PropertyChanged != null)
            { 
                PropertyChanged(this,new PropertyChangedEventArgs("DroppedDocument"));
                PropertyChanged(this, new PropertyChangedEventArgs("DroppedObject"));
            }
        }

Implement INotifyPropertyChanged interface, a little logic to prevent dropping unsupported data and we done. Now, in my application I can do the following


 



<Window.Resources>
    <XmlDataProvider x:Key="Test" Source="XMLFile1.xml" XPath="/root/node"/>
 
 </Window.Resources>
  <StackPanel>
    <local:ExcelDataReader Width="100" Height="100" Background="Yellow" x:Name="excelData"/>
    <local:MyGrid x:Name="myGrid"  Background="Blue" ItemsSource="{Binding Source={StaticResource Test}}"/>
  </StackPanel>

We done. Open an application, create some table in Excel, select in and drop into yellow rectangle. The information will be parsed and all grid columns will be created automatically. Isn't is really cool?


Source code for this article

Sunday, April 15, 2007

Converter and Validator in one place

Today, Doug asks about strange behavior with Binding Validation, while using custom converters. Follow XAML and C# code for his question:

 

<StackPanel>
    <StackPanel.DataContext>
      <x:Array Type="s:Int32" xmlns:s="clr-namespace:System;assembly=mscorlib">
        <s:Int32>0</s:Int32>
        <s:Int32>1</s:Int32>
      </x:Array>
    </StackPanel.DataContext>
    <TextBox Margin="3">
      <TextBox.Text>
        <Binding Path="[0]">
          <Binding.ValidationRules>
            <ExceptionValidationRule />
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>
    <TextBox Margin="3">
      <TextBox.Text>
        <Binding Path="[1]">
          <Binding.Converter>
            <local:MyInt32Converter xmlns:local="clr-namespace:myNamespace" />
          </Binding.Converter>
          <Binding.ValidationRules>
            <ExceptionValidationRule />
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>
  </StackPanel>
 
 

public class MyInt32Converter : IValueConverter
    {
        public object Convert(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            return value.ToString();
        }
 
        public object ConvertBack(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            return Int32.Parse(value.ToString());
        }
 
    }

Compile and run it. Then change value to something bad (e.g. "aaa") in first textbox - the validator will be fired and we'll get red border in our textbox. Then change the value of the second textbox - you'll get exception. Why this?


The answer is really simple - the sequence of binding is straight forward - get,convert,validate result. So, how to get this work with custom converters? Just convert it to validator. Following code in this converter will check and validate input value first, then, if the input is valid, convert and return results.


 



    [ValueConversion(typeof(String), typeof(Int32))]
    public class MyInt32Converter : ValidationRule, IValueConverter
    {
        public object Convert(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            return value.ToString();
        }
 
        public object ConvertBack(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            if (this.Validate(value, culture) == ValidationResult.ValidResult)
            {
                return Int32.Parse(value.ToString());
            }
            return value;
        }
 
 
 
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            int i=0;
            return new ValidationResult(Int32.TryParse(value.ToString(),out i),"The string is in incorrect format");
        }
    }

Have a nice day.

KB933493 does not comes with Office Update

For some reason the new patch for Outlook 2007 does not come with Windows Update as well as I was not notifies about the availability of this, in spite of the fact, that reported it. I run into the issue, and now it's fixed. My Outlook start to work much faster after it. Download (Validation required)

Friday, April 13, 2007

Check whether executable is managed

So, another interesting question: "How to check whether the executable file managed or not by code". Answering - ask framework about it version.

        [DllImport("mscoree.dll")]
        private static extern int GetFileVersion(
                        [MarshalAs(UnmanagedType.LPWStr)] string szFilename,
                        [MarshalAs(UnmanagedType.LPWStr)] StringBuilder szBuffer,
                        int cchBuffer,
                        out int dwLength);

But any, even very unmanaged file has file version - you ask. Any managed file has managed signature, so if the file is not managed, the method return COR_E_ASSEMBLYEXPECTED, else it return main core framework version.


 



        private string GetRuntimeVersion(string filename)
        {
            const int CCH = 0x400;
            StringBuilder sb = new StringBuilder(CCH);
            int len;
            int hr = GetFileVersion(filename, sb, sb.Capacity, out len);
 
            if (hr == unchecked((int)0x8007000B))
                return string.Empty; 
            else if (hr < 0)
                throw new Win32Exception(hr);
            else
                return sb.ToString(0, len - 1);
        }

Pretty simple, isn't is?


Source code for this article