Showing posts with label XAML. Show all posts
Showing posts with label XAML. Show all posts

Friday, 18 February 2011

How to use another control in UI as part of a control’s binding?

Lets say that you want to bind data based on one UI control, how would we go about doing it? I attempted one solution. First lets see how the output look like

image

As you can see it has a grid with 10 data items. At the bottom there is a label and text block, the text block shows how many items are in the grid. Also there is a filter button. Every time you press filter button, it will filter all the items in age increment of 20. So if I press filter then the result would be like the following

image

There is not data since the filter will look for age < 20. Now filter again

image

Now there will be 2 items,  where age is less than 40 and so on. The point we are interested in is the Total rows, as the number of rows changes the grid, the number also changes. Lets look the XAML how it is done.

XAML:

 1: <Grid x:Name="LayoutRoot" Background="White">
2: <sdk:DataGrid AutoGenerateColumns="true" Height="156" HorizontalAlignment="Left" Margin="35,24,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="285" />
3: <TextBlock Height="23" HorizontalAlignment="Left" Margin="190,214,0,0" Name="textBlock1" Text="{Binding ElementName=dataGrid1, Path=ItemsSource.Count}" VerticalAlignment="Top" Width="96" />
4: <Button Content="Filter" Height="23" HorizontalAlignment="Left" Margin="313,214,0,0" Name="Button" VerticalAlignment="Top" Width="75" Click="button1_Click" />
5: <sdk:Label Height="28" HorizontalAlignment="Left" Margin="35,209,0,0" Name="label1" Content="Total Rows" VerticalAlignment="Top" Width="133" />
6: </Grid>

The line we are interested in is line (3), look at the Text binding for text block. Here we say, we are want the source of data binding will be the data grid and, I am interested in total count of items source.

Lets look at the code behind on how the data binding happens and how filtering happens.

Code behind:

public partial class MainPage : UserControl
{
PagedCollectionView peopleCollection;
List<Person> people;
private int personNumber=1;

public MainPage()
{
InitializeComponent();
people = new List<Person>();
for(int i=0;i<10;i++)
people.Add(new Person() { Name = i.ToString(), Age = 20 + i * 10 });
peopleCollection = new PagedCollectionView(people);
dataGrid1.ItemsSource = peopleCollection;
}

private void button1_Click(object sender, RoutedEventArgs e)
{
peopleCollection.Filter = null;
peopleCollection.Filter = (p => ((Person)p).Age < (personNumber * 20));
personNumber++;
}
}

The code is simple and straight forward. I create a person collection and then converted it to PagedCollectionView and bind it to the grid. When user clicks Filter button, I clear the filter first then apply filter. There is no code in code behind which changes the text block count. It is all done in the XAML with stright control binding.

Monday, 14 February 2011

Making two cells in a grid dependable

One of my colleague was trying to create a Silverlight grid, where two columns are dependent. For sake of simplicity, lets say I have two columns, A and B. If A has a value in a valid range then Column B will be editable otherwise it is read only. Even though I said, it is columns, in reality it is cells. The reason I did not make the distinction in the beginning is that, it is taken care by binding. So how do we go about solve this problem. It is actually very easy than what we think. Now that we going to see the example, just want to point one out, I use Component One data gird a lot and I thought, I will bring in component one grid in this example as well.

1. First create a class where we will use this dependency. For simplicity I created a Person class as follows.

public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChanged("Name");
}
}

private double _amount;
private int _bonus;

public double Amount
{
get
{
return _amount;
}
set
{
_amount = value;
RaisePropertyChanged("Amount");
}
}

public int Bonus
{
get
{
return _bonus;
}
set
{
_bonus = value;
RaisePropertyChanged("Bonus");
}
}

public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

public event PropertyChangedEventHandler PropertyChanged;
}

In the above class, the two fields that we are interested in are Amount and Bonus. When the amount is more than 1000 then bonus field will become editable.

2. Create XAML to bind the collection as follows

 1:     <Grid x:Name="LayoutRoot" Background="White">
2: <Grid.Resources>
3: <converter:ColorConverterForReadOnly x:Key="converter1"/>
4: <converter:ReadOnlyConverter x:Key="converter2"/>
5: </Grid.Resources>
6: <c1:C1FlexGridExcel Name="_flex" AutoGenerateColumns="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
7: <c1:C1FlexGridExcel.Columns>
8: <c1:Column Binding="{Binding Name}"/>
9: <c1:Column Binding="{Binding Age}"/>
10: <c1:Column Binding="{Binding Amount, Mode=TwoWay}"/>
11: <c1:Column Binding="{Binding Bonus, Mode=TwoWay}" HorizontalAlignment="Right">
12: <c1:Column.CellTemplate>
13: <DataTemplate>
14: <Grid>
15: <Border Background="{Binding Amount, Converter={StaticResource converter1}}"/>
16: <TextBlock Text="{Binding Bonus}" VerticalAlignment="Center" HorizontalAlignment="Right"/>
17: </Grid>
18: </DataTemplate>
19: </c1:Column.CellTemplate>
20: <c1:Column.CellEditingTemplate>
21: <DataTemplate>
22: <Grid>
23: <Border Background="{Binding Amount, Converter={StaticResource converter1}}"/>
24: <TextBox IsReadOnly="{Binding Amount, Converter={StaticResource converter2}}" Text="{Binding Bonus, Mode=TwoWay}"
25: Background="{Binding Amount, Converter={StaticResource converter1}}" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0"/>
26: </Grid>
27: </DataTemplate>
28: </c1:Column.CellEditingTemplate>
29: </c1:Column>
30: </c1:C1FlexGridExcel.Columns>
31: </c1:C1FlexGridExcel>
32: </Grid>

In the above code,you will notice I am using two converters. First converter will set the back ground color to readonly background color when the amount is less than 1000. The second converter will return true or false based on amount field.

So if you would notice, from line 12 – 28, have two definitions. one for cell template to render the data and cell edit template which will be used when user try to edit the cell.

In cell template, line 15, will set the back ground of the cell to read only look based on the amount. As you can see, Background is bound to amount but the brush will be set in the converter. (will see the converter later).

Line 16 basically shows a text block which by default readonly to show the data.

Line 23 is same as line 15 to change the background color.

Line 24 is a text box which will allow the user to edit the cell if the amount is greater than 1000. This is achived by IsReadOnly property, which is bound to Amount again, when it is passed through the converter, it will check and see if the amount is greater than 1000 and based on the value, it will return true or false. So if the amount is less than 1000 then isreadonly will be true thus making the cell readonly.

3. The Converters:

 1: public class ColorConverter : IValueConverter
2: {
3: public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
4: {
5: double? data = value as double?;
6:   7: if (data == null || data < 1000)
8: {
9: return new SolidColorBrush(Color.FromArgb(255, 200, 200, 200));
10: }
11: else
12: return null;
13: }
14:   15: public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
16: {
17: return null;
18: }
19: }
20:   21: public class ReadOnlyConverter : IValueConverter
22: {
23: public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
24: {
25: double? data = value as double?;
26:   27: return (data == null || data < 1000);
28: }
29:   30: public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
31: {
32: return value;
33: }
34: }

The first converter is background color converter called ColorConverter. Which is used in the background property binding. As you can see in line 7, when the amount is less than 1000, then it returns gray background brush. Other wise, it returns null.

The second converter (line 21) is read only converter. This will return true, when the amount is less than 1000 otherwise is false at line 27.

Tuesday, 1 February 2011

How to order columns while binding to Grid?

One of my silverlight newbie friend asked, when I have a collection of data that I bind to grid and want to autogenerate columns based on the class it is bound to but I want to control the order it appear and also I do not want to display some of the columns. It was funny when he asked me I was stumped since I never did this kind of binding. Well, it is not that difficult. ComponentModel is here to help. So here is how we do it.

1. Create you XAML which which will have nothing but a Grid like the following

   1:  <sdk:DataGrid Name="_normal" AutoGenerateColumns="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="0">
   2:  </sdk:DataGrid>  
2. Create your model. In my case I created a person class. 
public class Person: IDataErrorInfo
{
[StringLength(20)]
[Display(Description="Name", Order=1)]
public string Name { get; set; }

[Range(0, 100)]
[Display(Description="Age", Order=2)]
public int Age { get; set; }

[Display(Description="Address 1", Order=5)]
public string Address1 { get; set; }

[Display(Description = "Address 2", Order = 4)]
public string Address2 { get; set; }

[Display(Description="Amounts", Order=3)]
public double Amount {get; set; }

[Display(AutoGenerateField=false)]
public double DoNotShowThisField {get; set; }

private double doNotShowThisField1;
}

Now you create your data collection and bind it to your gird. Your output would be something like the following

image

The attribute, Display sets the column order to display in the grid. As you can see Amount comes last in the class property but the Display order was set to 3 so it appears at 3rd column. Also notice DoNotShowThisField, even though it is declared as public, the display attribute says, when autogenerate fields for data binding, do not include this field.

Hope this helps.

Friday, 14 January 2011

AutoCompleteBox to auto complete data entry.

I ran into a situation where, I need a way to provide a user with auto complete text box with predefined list of data. If the data already exists in the list, then user select it and continue, if the data does not contain in the list, the user data will be added to the list thus it is available for the future auto completion. It is pretty simple, if you know there is a control called ‘AutoCompleteBox’ available in the toolkit.

In my case the auto completion is part of the grid, so I will stay on the same sample to illustrate how easy to implement this. This is a very rudimentary implementation and can be improved a lot to meet your needs.

First thing we start by adding a cell edit template to the the column in question where we need to provide the auto completion. A simple XAML definition would look like the following

 1: <c1:Column.CellEditingTemplate>
2: <DataTemplate>
3: <sdk:AutoCompleteBox Text="{Binding ReferencePath,Mode=TwoWay}"
4: Populating="tester_Populating" MinimumPrefixLength="0" IsDropDownOpen="True" LostFocus="tester_LostFocus"/>
5: </DataTemplate>
6: </c1:Column.CellEditingTemplate>

There are few important pieces in line 4. The first one is the event called ‘tester_Populating’ event. This event, will filter the data collection and return only matching values as the user types to show in the drop down.

MinimumPrefixLength to 0 will show the drop down even when there is no data entered in the text box.

IsDropDownOpen set to true will open the drop down on double click on the cell.

LostFocus event will trigger a method to check and see if the data user enter already in the list and if it is not available then add it to the collection to show in the future.

Lets look at the events implementation

tester_Populating:

 1: private void tester_Populating(object sender, PopulatingEventArgs e)
2: {
3: if (_names == null)
4: {
5: _names = new List<string>();
6: _names.Add("Kumar");
7: _names.Add("Ram");
8: _names.Add("Krishna");
9: _names.Add("John");
10: }
11: List<string> data = new List<string>();
12: AutoCompleteBox box = sender as AutoCompleteBox;
13: foreach (string name in _names)
14: {
15: if (name.ToUpper().StartsWith(box.Text.ToUpper()))
16: data.Add(name);
17: }
18: box.ItemsSource = data;
19: box.PopulateComplete();
20: }

Line 3-10 to create first time collection. Line 13-17 is where you check and see if the user input data matches any in our collection and if so, then add it to temperory collection and binding it to auto complete box. As done in line 18.

 1: private void tester_LostFocus(object sender, RoutedEventArgs e)
2: {
3: AutoCompleteBox box = sender as AutoCompleteBox;
4: if (box.Text.Length > 0)
5: {
6: bool found = false;
7: foreach (string name in _names)
8: {
9: if (name.ToUpper().Equals(box.Text.ToUpper()))
10: {
11: found = true;
12: break;
13: }
14: }
15: if (!found)
16: _names.Add(box.Text);
17: }
18: }

Hope this helps.

Thursday, 6 May 2010

RAD development in SL4

It is very easy to develop a simple silver light page which does not have any complicated logic without writing a single line code (as my friend put it). Yes it is very easy. But there is another question, if you are simply displaying a data from data source then do you need to follow MVVM model? You could but I do not think it is required since there is no code involved in VM. This is simple forms over data pattern.

In SL4, at your server side, create the Data Model and associate it with a service.  Build the solution. Now go to the client side and under Data there is an option to show the Data Source. From the data source, under domain services, drag the data service you want and drop in the grid control, whola that is it. Now if you run the program it will show you a simple data grid with the data from the domain service.

Couple of things you have remember if you are going in this path. The first and foremost, is the authentication and authorization.  If you do not care about the authorization then you are ok, if not, build the base authorization service and reuse it in all the domain services by adding [RequiredAuthentication] attribute. In the client side also create the main page which knows user information and passes to authentication service before you do data binding.

Second issue that you might run into is the amount of data that is passed over wire. So apply some filtering to the data before you return all the data. You can do the filtering at the server side or on the client side. You can do the filtering in the XAML itself. The simple solution is to add the filter based on some controls. By adding some controls, which binds the values to filtering logic in the data grid will bring only the data you want to show. You can read more about how to apply filtering on XAML at this link.