Sunday, February 27, 2011

Hide Extra Blank Column from WPF DataGrid

If you have worked with WPF DataGrid you will be familiar with the problem of extra blank column which appears after last column inside DataGrid. It looks ugly and definitely you would like to get rid of that column. In this post I am going to talk about two ways to hide that extra column. First approach will be based on popular MVVM pattern and second will use a simple event in code behind file to hide that column. You can download a ready to run  code from here which demonstrate both scenarios.

In both scenarios I am setting code behind file as DataContext for my MainWindow and I am binding my DataGrid with a property 'MyView', declared in code behind file, which returns a DataTable as DataView.

   1:  public DataView MyView
   2:  {
   3:       get
   4:       {
   5:            DataTable table = new DataTable();
   6:            DataColumn column1 = new DataColumn("Column1");
   7:            DataColumn column2 = new DataColumn("Column2");
   8:            table.Columns.Add(column1);
   9:            table.Columns.Add(column2);
  10:            //create ten rows
  11:            for (int i = 0; i < 10; i++)
  12:            {
  13:                 DataRow newRow = table.NewRow();
  14:                 newRow["Column1"] = i.ToString();
  15:                 newRow["Column2"] = (i + 100).ToString();
  16:                 table.Rows.Add(newRow);
  17:            }
  18:       return table.AsDataView();
  19:       }
  20:  }

In MVVM based scenario I am encapsulating my DataGrid inside a Grid. Inside Grid you have to declare same number of columns as in your DataView. I am declaring two columns in my Grid because I have two columns in my DataView. I am setting width of my Grid's columns equal to the actual width of columns inside DataGrid.

   1:  <Grid>
   2:       <Grid>
   3:            <Grid.ColumnDefinitions>
   4:                 <ColumnDefinition Width="{Binding ElementName=Col1, Path=ActualWidth}"/>
   5:                 <ColumnDefinition Width="{Binding ElementName=Col2, Path=ActualWidth}"/>
   6:            </Grid.ColumnDefinitions>
   8:            <DataGrid Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Path=MyView}" AutoGenerateColumns="False">
   9:                  <DataGrid.Columns>
  10:                      <DataGridTextColumn MinWidth="100" x:Name="Col1" Header="Column1" Binding="{Binding Path=Column1}"/>
  11:                      <DataGridTextColumn MinWidth="100" x:Name="Col2" Header="Column 2" Binding="{Binding Path=Column2}"/>
  12:                 </DataGrid.Columns>
  13:            </DataGrid>
  14:       </Grid>
  15:  </Grid>
That's all, it will do the trick. You won't see any ugly extra column inside your DataGrid.

The second scenario, a non-MVVM based scenario, is sample. I am just handling DataGrid_AutoGeneratingColumn' event in code behind file.

   1:  <DataGrid ItemsSource="{Binding Path=MyView}" AutoGenerateColumns="True" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn"/>
In the actual event handler I am setting width of last column of MyView equal to 'Star'. Star in terms of WPF Grid Length means take all available space. So the last column of DataGrid will take all available space including space of extra blank column and as a result you won't see any extra blank column in DataGrid.

   1:  private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
   2:  {
   3:       if(e.Column.Header == "Column2")
   4:            e.Column.Width = new DataGridLength(1,  DataGridLengthUnitType.Star);
   5:  }
These are the two simple ways to get rid of the extra blank column which appear inside WPF DataGrid. You can download ready to run code I showed above from here.

Sunday, February 20, 2011

IsMouseOver Trigger not working in WPF

In this post I will explain the problem I faced when I tried to handle IsMouseOver property of a Button in WPF and the solution or right way to do. I was working on a Window which had a Button and I wanted to override the default color schemes related to Button. For example, if you put your mouse over a Button in WPF, by default Button's background color will change light blue. I wanted to override this light blue color with some color of my own choice and similarly I wanted to change background color of Button when button goes in to pressed state.

First piece of XAML I wrote to achieve the change in background color on mouse over was this.

   1:   <Button Width="100" Height="50" Content="Click Me!">
   2:        <Button.Style>
   3:             <Style TargetType="{x:Type Button}">
   4:                 <Style.Triggers>
   5:                      <Trigger Property="IsMouseOver" Value="True">
   6:                           <Setter Property="Background" Value="LightGreen"/>
   7:                      </Trigger>
   8:                 </Style.Triggers>
   9:            </Style>
  10:       </Button.Style>
  11:  </Button>
I ran my application and on putting mouse over the Button I saw same old light blue color in Button's background instead of light green which I specified in XAML. When I looked closely I found out that on putting mouse over Button, for just a moment, Button's background color was changing to green and then immediately it changed to light blue. So what was I missing or doing wrong?

On digging deeper in to this issue I found out that Button in WPF has a default control template. Button was changing it's background color to light blue according to that default control template. When I applied my own trigger on Button, saying that change background color to light green when mouse comes over, as a result two triggers existed for the Button at same time. First one was the WPF's default(which changes background color to light blue) and second was the one which I defined in XAML. As WPF supports multiple triggers on same property, both triggers were working perfectly fine with one another. I think my custom trigger had precedence over the default one thats why WPF was applying my custom trigger first, because of which I was able to see light green background for a moment. After that WPF applied the second trigger, the WPF's default one, which changed the background color of Button to light blue.

The correct way to override the Button's default behavior is by overriding default control template. This can be done by following XAML below.

   1:  <Button Width="100" Height="50" Content="Click Me!">
   2:       <Button.Template>
   3:            <ControlTemplate TargetType="{x:Type Button}">
   4:                 <Border x:Name="bdr_main" CornerRadius="20" Margin="4" BorderThickness="1" BorderBrush="Black" Background="LightGray">
   5:                      <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" Margin="8,6,8,6" ContentSource="Content" />
   6:                 </Border>
   7:                 <ControlTemplate.Triggers>
   8:                      <Trigger Property="IsMouseOver" Value="True">
   9:                           <Setter TargetName="bdr_main" Property="Background" Value="LightGreen"/>
  10:                      </Trigger>
  11:                      <Trigger Property="IsPressed" Value="True">
  12:                           <Setter TargetName="bdr_main" Property="Background" Value="Red"/>
  13:                      </Trigger>
  14:                 </ControlTemplate.Triggers>
  15:            </ControlTemplate>
  16:       </Button.Template>
  17:  </Button>
As you can see in above XAML, instead of creating triggers on top of default control template, I am now defining the triggers inside overridden control template. Once you define your own control template, you gain full control of a control's default behavior and layout. In XAML above I have changed the default rectangular shape of WPF's Button to a Button which is more round or circular from the corners.
You can download a ready to run sample based on this post from here.

Sunday, February 13, 2011

ScrollViewer causing Slow Performance in WPF

In this post I am going to talk about the performance hit you might get in your WPF application if you are using ScrollViewer. ScrollViewer is used in WPF to display content in a smaller area as compared to content's actual size. In other words, if your content is large and you want to display it in a smaller area, you can put your content inside a ScrollViewer. ScrollViewer will then use scroll bars to display your complete content in smaller area.

If you are planning to use ScrollViewer or you are already using it in your application you might face some performance hit. This performance hit will only be visible if the content you are placing inside ScrollViewer is too large. You can download a ready to run application from here to see the performance hit with and without ScrollViewer. In my case, I am using ScrollViewer around a DataGrid whose ItemsSource is a DataTable. My DataTable contains 1000 rows. 1000 rows are more than enough to make sure that the content is large and it can't be displayed completely without scroll bars.
In my sample application's main view I have a Grid which contain two rows. First row has a Button while second row has a DataGrid enclosed inside a ScrollViewer.

   1:  <Grid>
   2:       <Grid.RowDefinitions>
   3:            <RowDefinition Height="30"/>
   4:            <RowDefinition/>
   5:       </Grid.RowDefinitions>
   6:       <Button Grid.Row="0" Click="Button_Click">Populate Grid</Button>
   7:       <ScrollViewer Grid.Row="1">
   8:            <DataGrid Grid.Row="1" Name="dataGrid" HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"/>
   9:       </ScrollViewer>
  10:  </Grid>
As you can see in XAML above, I am handling click event of button. In this event I am creating a DataTable with 1000 rows and setting this DataTable as ItemsSource of my DataGrid.
If you run the application I specified earlier, you will see a "Populate Grid" button on top. Click this button to populate the DataGrid. By default, as you can see in XAML, DataGrid is inside ScrollViewer. When you will click "PopulateGrid" button, WPF will take around 5 seconds(at least on my system) to display the content of DataGrid. Now let's do a slight change in XAML and get rid of ScrollViewer.

   1:  <Grid>
   2:       <Grid.RowDefinitions>
   3:            <RowDefinition Height="30"/>
   4:            <RowDefinition/>
   5:       </Grid.RowDefinitions>
   6:       <Button Grid.Row="0" Click="Button_Click">Populate Grid</Button>
   7:       <!--<ScrollViewer Grid.Row="1">-->
   8:            <DataGrid Grid.Row="1" Name="dataGrid" MinRowHeight="25" AlternatingRowBackground="LightGray" MinColumnWidth="100" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ColumnHeaderHeight="35"/>
   9:       <!--</ScrollViewer>-->
  10:  </Grid>
As you can see I just have commented out the ScrollViewer. Now DataGrid is directly inside second row of Grid. After making these changes run the application again and click the populate button. This time WPF will display content of DataGrid without any delay or taking anytime.

So what could be the reason behind this performance hit? I think when we are placing the DataGrid inside ScrollViewer, all default UI Virtualizations of DataGrid are getting void as it is now inside ScrollViewer and as a result UI is taking so much time to load the contents of DataGrid.
You can download the ready to run application from here and play with both scenarios.

Monday, February 7, 2011

Faster Controls with UI Virtualization in WPF

A very useful feature in WPF for making faster and interactive UIs is UI virtualization. Let's say, in your WPF application, you have a ComboBox with 10,000 elements in it. When you will click the ComboBox to expand it, it will take quite some time before actually the ComboBox's available options will be displayed in a drop down for selection. The reason is simple, loading 10,000 UI elements in WPF's visual tree naturally takes some time. One thing to keep in mind is that whether your combo box contains 10K element or 100K elements, user will be looking at a very limited number of options at one instance of time. For example if you expand a combo box, at one time you will only be looking at approximately 25 elements. Even if at one time we see only limited amount of items, WPF loads all the item in visual tree when you click expand button and it takes memory and time. By enabling UI virtualization in your ComboBox, WPF will create only those elements in memory which you can see at one time. This will make combo box expand in no time and as a result even if your combo box contains 10K elements, WPF will take no time to expand it. As you will move the scroll bar down to see more items in ComboBox, WPF will dynamically dispose the elements which will go out of the view as a result of scrolling and will create & load new items in ComboBox accordingly. 
Some controls uses UI Virtualization by default like ListBox and ListView but some controls don't for example ComboBox. You can use following XAML to enable UI virtualization in a combo box and similarly in any control you want.

   1:  <ComboBox ItemsSource="{Binding Path=Collection}">
   2:       <!--comment ComboBox.ItemsPanel below to see how much time WPF takes to load this ComboBox without UI virtualization-->
   3:       <ComboBox.ItemsPanel>
   4:            <ItemsPanelTemplate>
   5:                 <VirtualizingStackPanel />
   6:            </ItemsPanelTemplate>
   7:        </ComboBox.ItemsPanel>
   8:  </ComboBox>
I have uploaded an application which you can use to see the difference in loading time with and without UI virtualization. You can download this application from here. Comment the ComboBox.ItemPanel in XAML to note the time it takes without UI Virtualization and uncomment ComboBoc.ItemPanel to note time it takes with UI Virtualization.

Sunday, February 6, 2011

Using as a Search Engine

I would like to share an experience of mine I had recently in this post. If you are a person who google every now and then to find sometimes a very specific thing related to a specific topic, you might find this post useful and would like to give it a shot.
I was working on a WPF based application where I had to display some data in a very specific format to user. I knew that someone must have done it before so I was trying to find some code snippet related to it that I can use or get an idea from for my WPF application. I tried Google, Bing and even Yahoo to to find that code snippet but I couldn't. At this point I would like to tell that I use for storing bookmarks like thousands of other out there. So after wasting quite sometime on search engines an idea came in my mind "Why not try to find it on Delicious?". I extracted the main keywords from my problem and searched them on delicious. I typed only the keywords because I know delicious uses the tags to store and organize links. I couldn't believe it and I expect same from you but the first link delicious returned had the solution of my problem :) Delicious have a pretty big and i must say organized database of useful links. I am not sure how many users have recently switched from delicious because of the news that yahoo is closing it down but it still have huge collection of refined, selective and tagged links. If you ever get stuck in something and search engine won't return you what you are looking for(which I wish don't happen to anyone) give this approach a shot and see how it goes.