Saturday, April 28, 2007

Snipping tool integration plugin for WLW

 Playing a lot with Live Writer API, I found a lot of nice features and possibilities, as well as not-so-nice problems, but one thing is really clear - writing plugins for Windows Live Writer is fun and quick work. Let's take an example of Yossi's request. It took me about a half an hour to write it, or today's plugin for snipping tool integration - 25 minutes, including installer. Don't is really nice?

OK, so, you can download it - this plugin integrates Windows Snipping Tool (Windows Vista and Windows XP tablet edition) into Live writer. All you have to do is click "Insert snip capture" from right or upper menu and the application invokes Snipping Tool. After the screenshot will be taken, the image will be inserted automatically into your current post. Then, you can manage is as regular image inside the Writer (and that's really great feature). So easy, so fast, so useful. Download and use it.

snipbig

Friday, April 27, 2007

Is "web scrapping" legal?

Most of those fun webparts widgets gadgets use technology named "web scrapping" (an ability to "wrap off" content from web sites and display it within own layout). But not all of those gadgets provided by real content owners, most of them were developed by someone else. And almost no one of those developers has legal permissions for using and distributing content this way. So I have a question: "Is it legal?"

From one hand, such gadgets, as well as RSS feeds provide outstanding push for content distribution, so owners really love and support developers to use their content. From the other hand, if everything is accessible alternative way, why to visit owner's web site, so users will no see and click valuable ads there. So, is gadget - a good thing for content providers?

As you, probably, know, I was the first person is Israel, who fully implement EIP. It was about 8 years ago. Days, when Shay Agassi tried to sell me his TopTier, days when YNet asked for big bucks for their news content to be integrated into intranet portal (now they have free RSS)

Maybe, I'm bit outdated, but when 've published Israel Traffic Information gadget, I asked for fax permission of IBA to be sure about the legality of my stuff.

Today, I wrote another gadget, that display gas price information all over the US by using information provided by OPIS. I asked MSN legal guys about this and they did not understand what I'm talking about. "If it's public domain, use it, but be sure to provide link to the owner of the information" - they responded.

Am I odd fish? Maybe, but I'm still waiting for MS partner legality team to send me written authorization for using their partner information. When I'll get it, I'll sleep better with work I done.

gaspricesbig

Thursday, April 26, 2007

RTL and LTR in Windows Live Writer

Someone is crying for a couple of days about text direction support in WLW. I decided to help all those, who have to wait for MS to understand, that there are right-to-left languages in the market, that even with really good HTML/RichText control we should support it. So, I decided to write simple plugin for Live Writer and change the direction of input to RTL or LTR, send Change Input Language signal to the workspace and add two magic tags, Yossi is very worry about dir="rtl" and align="right". So, start blogging easily, Hebrew men :)

image

P.S. Due to bug with v1.0.1(6) [the official release], after clicking "Insert RTL Block" or "Insert LTR block", you should click the workspace to get back and write. In next build, this annoying bug will be fixed.

Download Text Direction Change Plugin for Windows Live Writer.

Sunday, April 22, 2007

Thread safe observable collection

If you ever wrote something more, then standard UI input-output, you sure got into wide range of exceptions like "The calling thread cannot access this object because a different thread owns it" or "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread". What the problem? The problem is old like condom in my wallet world-old, the try to get into resources from different threads. Let me show you some code. Very-very bad code. It's unbelievable, that this code can ever work.

void onAdd(object sender, RoutedEventArgs e)
{
new Thread(new ParameterizedThreadStart(addProc)).Start(Resources["source"] as MyCollection);
}
void onRem(object sender, RoutedEventArgs e)
{
new Thread(new ParameterizedThreadStart(remProc)).Start(Resources["source"] as MyCollection);
}
void onMov(object sender, RoutedEventArgs e)
{
new Thread(new ParameterizedThreadStart(movProc)).Start(Resources["source"] as MyCollection);
}

<Window.Resources>
<
l:MyCollection x:Key="source"/>
</
Window.Resources>
<
StackPanel DataContext="{StaticResource source}">
<
Button Click="onAdd">Add</Button>
<
Button Click="onMov">Move</Button>
<
Button Click="onRem">Remove</Button>
<ListBox ItemsSource="{Binding}"/>
</
StackPanel>

 


Does not it looks scare? Such code might open unbelievable number of thread. Nothing will stay for it. But, wait. Let's try to imagine, that we have a bunch of system methods, such as RAM queries, or kind of Amazon web service with million of transactions per second. Even very smart programmer will work with at least 10 threads to update the same collection. So, what to do?


Let's write Thread Safe Observable Collection, that knows, that it might be called from big number of threads and take care on itself. Sounds good? Let's start.


First of all we'll try to get current STAThread (UI) dispatcher. How to do it? Simple. The only place we'll initialize our collection is in XAML, so default constructor will be called from the thread we need. Now, we save it


Dispatcher _dispatcher;
public ThreadSafeObservableCollection()
{
_dispatcher =
Dispatcher.CurrentDispatcher;
}

 


The next step is to override all vital methods of our collection to be invoked from this thread.


        protected override void ClearItems()
{
if (_dispatcher.CheckAccess())
{
base.ClearItems();
            }
else
{
_dispatcher.Invoke(
DispatcherPriority.DataBind, (SendOrPostCallback)delegate { Clear(); });
}
}

 


 Let's understand it. First we're checking if the current thread can access the instance of the object, if it can - we'll just do our work, but if not, we'll invoke it in the context of the current thread by sending anonymous delegate with or without parameters.


Why I'm casting the anonymous delegate into SendOrPostCallback? The answer is simple - look into Reflector. You can not implement your delegate with parameter and without return value better :)


The next step is to take care on locking. We can use old Lock(object) method, but why to do it, if I can continue read my information, while someone writing it. Let's use ReaderWriterLock. This class makes us able to write and read information concurrently, but still preventing us from writing things at the same simple. Due to the fact, that we are using this lock instance inside at one place we can use UpgradeToWriterLock and DowngradeFromWriterLock instead of real locking. Let's see how to do it


        protected override void InsertItem(int index, T item)
{
if (_dispatcher.CheckAccess())
{
if (index > this.Count)
return;
LockCookie c = _lock.UpgradeToWriterLock(-1);
base.InsertItem(index, item);
_lock.DowngradeFromWriterLock(
ref c);
}
else
{
object[] e = new object[] { index, item };
_dispatcher.Invoke(
DispatcherPriority.DataBind, (SendOrPostCallback)delegate { InsertItemImpl(e); }, e);
}
}
void InsertItemImpl(object[] e)
{
if (_dispatcher.CheckAccess())
{
InsertItem((
int)e[0], (T)e[1]);
}
else
{
_dispatcher.Invoke(
DispatcherPriority.DataBind, (SendOrPostCallback)delegate { InsertItemImpl(e); });
}
}

 


Sure, you have to initialize _lock = new ReaderWriterLock() in the constructor. Pay attention, that I'm using the same delegate even with two parameters. It still much better and faster, that write your own delegates for each type of action. Let the framework to do work for you.


After overwriting all vital methods for ObservableCollection, we're actually, finished. Now, you can derive from your new class and perform the code at the beginning on the article very fast. The smart thread safe observable collection takes care on all the least.


Download and run following example, after playing a bit, check "Fast" checkbox and start pressing buttons as wide spirit. The application will work as requested.


Source code for this article

Friday, April 20, 2007

New source copy helper for Windows Live Writer

Are you already tiered of BlogMyCode or CopyAsHTML popup windows and non-standard right-mouse-click-do-something-then-maybe-I-help-you dialogs? Let me introduce my last plugin for Windows Live Writer blog client - Code Past Plugin. It's really simple as it should be. This plugin uses standard copy functions from anywhere (no only from VS2005). Then it converts code into XHTML and put it into WLW window. Don't this should work this way? Without right clicks and unnecessary windows? Download it now and use it. Here the sample format of code. You can optionally add header or line numbering. You can even break lines (if you wish). The other feature I added is new clipboard detection, so if you has something really wrong there and already opened code adding dialog, you don't have to close it and copy something else, just switch to Studio, copy new code and the plugin will detect all your changes.

Code Snippet
        private void btOK_Click(object sender, EventArgs e)
{
this.Close();
}

string getOptionText()
{
if (actCode != string.Empty)
{
StringBuilder sb = new StringBuilder();
if (cbLinesNum.Checked)
{
string[] outp = actCode.Replace(@"\r\n", "\r").Split('\r');

int offset = 0;
for (int i = 0; i < outp.Length; i++)
{
if (outp[i].Trim() == string.Empty)
{
sb.Append(
"<br/>");
offset++;
}

 


Here it's configuration window with preview.


image


If you catch bugs or have feedbacks, please notice me via email or in comment to this post. I'll appreciate you :)


Download Code Past Plugin (372 KB, MSI installer)

Thursday, April 19, 2007

How to read GPS metadata from image

Someone asks about how to get GPS (and other) metadata from Image properties. It's easy once we have Windows Imaging Component, which now the integrate part of .NET framework 3.0, so you can get meta information directly from your image. How to do it? First of all, you should get BitmapMetadata out from your source. In order to do it you should use BitmapDecoder, there are some of them, one for each image type (Jpg, Bmp, Png, Gif etc). So let's take Jpeg for now.

            using (Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                JpegBitmapDecoder decoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.None);
                BitmapMetadata meta = (BitmapMetadata)decoder.Frames[0].Metadata;
                ulong[] latitude = meta.GetQuery("/app1/ifd/gps/subifd:{ulong=2}") as ulong[];
                ulong[] longitude = meta.GetQuery("/app1/ifd/gps/subifd:{ulong=4}") as ulong[];
            }

Next step is to convert weird ulong array into readable coordinates. Simple calculation in static method for each array and you'r done. I will not enter the math calculation and explain them. Just use it "as-is". They are rather precise, trust me :)



static double ConvertCoordinate(ulong[] coordinates)
        {
            int lDash = (int)(coordinates[0] - ((ulong)0x100000000));
            int lF = (int)(coordinates[1] - ((ulong)0x100000000));
            double lR = ((double)(coordinates[2] - ((ulong)0x6400000000))) / 100;
            double tRes = (lDash + (((double)lF) / 60)) + (lR / 3600);
            return (Math.Floor((double)(tRes * 1000000)) / 1000000);
        }

We finished, you'r got your coordinates in decimal variation, e.g. 26.34433:37.332344. Have a nice day

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