代码改变世界

自定义控件为了虚拟化表结构

2012-06-01 16:17  血糯米Otomii  阅读(273)  评论(0编辑  收藏  举报

I’ve just Copied the Article of paul van bladel for sharing, original link:

http://blog.pragmaswitch.com/?p=318

 

Introduction

LightSwitch has a powerful and robust set of standard controls which are doing a great job. Apart from that there is always the possibility the create your own custom controls.

That’s what we will do in this post.

I want to demonstrate a way to visualize data of rooms and their corresponding reservations on a kind of “plan board”. This differs radically from a normal datagrid because the visualization that we have in mind has kind of undefined amount of “columns.

The Data model

We have a pretty simple Parent-child relationship between rooms and reservations.

For completeness, here the Reservation entity design:

As you can see a reservation has a slot property, which is the reservation date. A room can only reserved for at least a full day. Apart from the link to the room in question, a reservation has as well a state property. In my simple example this can be A, B or C, but you can use your imagination to think about something more realistic.

The classic list-detail representation

How do we want to visualize the room reservations?

I will not focus here on a graphical masterpiece, I limit things to a spartan representation in such a way all attention can go to the necessary plumbing.

You can click on a current reservation and a modal form will pop up:

The necessary ingredients

The Xaml

<UserControl xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"  x:Class="LightSwitchApplication.ReservationSilverlightControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    mc:Ignorable="d" xmlns:local="clr-namespace:LightSwitchApplication"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <local:PeriodParameters x:Key="PeriodParameters"></local:PeriodParameters>
        <local:ReservationValueConverter x:Key="ReservationValueConverter"></local:ReservationValueConverter>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White"  DataContext="{Binding Screen}"  Loaded="LayoutRoot_Loaded" >
        <sdk:DataGrid  AutoGenerateColumns="False" x:Name="grid"  ItemsSource="{Binding Rooms}">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn
                             Binding="{Binding Name}"
                             Header="Name"/>
                <sdk:DataGridTemplateColumn   Width="*">
                    <sdk:DataGridTemplateColumn.HeaderStyle>
                        <Style
                          TargetType="primitives:DataGridColumnHeader">
                            <Setter
                              Property="HorizontalContentAlignment"
                              Value="Stretch" />
                            <Setter
                              Property="VerticalContentAlignment"
                              Value="Stretch" />
                            <Setter Property="Margin"
                                         Value="0" />
                            <Setter Property="ContentTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <ItemsControl
                          ItemsSource="{Binding Source={StaticResource PeriodParameters},Path=DateList,Mode=TwoWay}">
                                            <ItemsControl.ItemsPanel>
                                                <ItemsPanelTemplate>
                                                    <StackPanel
                                         Orientation="Horizontal">
                                                    </StackPanel>
                                                </ItemsPanelTemplate>
                                            </ItemsControl.ItemsPanel>
                                            <ItemsControl.ItemTemplate>
                                                <DataTemplate>
                                                    <Border  Width="25" >
                                                        <TextBlock Text="{Binding }"
                                              TextAlignment="Center"/>
                                                    </Border>
                                                </DataTemplate>
                                            </ItemsControl.ItemTemplate>
                                        </ItemsControl>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </sdk:DataGridTemplateColumn.HeaderStyle>
                    <sdk:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ItemsControl ItemsSource="{Binding Converter={StaticResource ReservationValueConverter},  ConverterParameter={StaticResource PeriodParameters}}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate  >
                                        <StackPanel   Orientation="Horizontal"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <StackPanel>
                                            <Border Width="25">
                                                <TextBlock Height="25" Width="25" TextAlignment="Center"
                                                           MouseLeftButtonDown="TextBlock_MouseLeftButtonDown"
                                                           Tag="{Binding Id}"  Text="{Binding State}">
                                                </TextBlock>
                                            </Border>
                                        </StackPanel>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellTemplate>
                </sdk:DataGridTemplateColumn>
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>
    </Grid>
</UserControl>

You’ll notice that my control is basically a datagrid with 2 columns where the first column (a DataGridTextColumn)  is bound to the room visual collection and the second column ( a DataGridTemplateColumn) is in fact a stackpanel which “horizontalises” the reservations for the current room.

One difficulty is here that a room does not necessary have reservations for the complete time frame for which the overview is requested. Also finding out the right way to do the binding was, at least for me, a challenge.

The Xaml Code behind

public partial class ReservationSilverlightControl : UserControl
    {
        public ReservationSilverlightControl()
        {
            InitializeComponent();
            PeriodParameters.DateList = new ObservableCollection<string>();
        }
 
        private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            TextBlock textBlock = sender as TextBlock;
            var context = (IContentItem)this.DataContext;
            var screen = (IScreenObject)context.Screen;
 
            if (textBlock.Tag != null)
            {
                int myReservation = int.Parse(textBlock.Tag.ToString());
 
                screen.Details.Dispatcher.BeginInvoke(() =>
                    {
 
                        screen.Details.Properties["CurrentReservationId"].Value = myReservation;
                        screen.Details.Methods["EditDetails"].CreateInvocation(null).Execute();
                    });
            }
            else
            {
                //here we could  call a create reservation screen.
            }
 
        }
 
        private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
        {
            if (((sender as Grid).DataContext as SimpleCustomControl).StartDate.HasValue)
            {
                PeriodParameters.StartDate = ((sender as Grid).DataContext as SimpleCustomControl).StartDate.Value.Date;
            }
            else
            {
                PeriodParameters.StartDate = DateTime.Now.Date;
            }
 
            if (((sender as Grid).DataContext as SimpleCustomControl).EndDate.HasValue)
            {
                PeriodParameters.EndDate = ((sender as Grid).DataContext as SimpleCustomControl).EndDate.Value.Date;
            }
            else
            {
                PeriodParameters.EndDate = DateTime.Now.AddDays(31).Date;
            }
 
            TimeSpan span = PeriodParameters.EndDate - PeriodParameters.StartDate;
 
            for (int i = 0; i <= span.Days; i++)
            {
                PeriodParameters.DateList.Add(PeriodParameters.StartDate.AddDays(i).Day.ToString());
            }
        }
    }

The row ValueConverter

public class PeriodParameters
    {
        public PeriodParameters() { }
        public static ObservableCollection<string> DateList { get; set; }
        public static DateTime StartDate { get; set; }
        public static DateTime EndDate { get; set; }
    }
 
    public class ReservationValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            ObservableCollection<Reservation> reservationObservableCollection = new ObservableCollection<Reservation>();
            Room currentRoom = value as Room;
            var query = currentRoom.ReservationsQuery as IExecutableWithResult;
            query.ExecuteCompleted += new EventHandler<ExecuteCompletedEventArgs>((s, e) =>
            {
                IEnumerable<Reservation> reservationList = query.Result as IEnumerable<Reservation>;
 
                DateTime startDate = PeriodParameters.StartDate.Date;
                DateTime endDate = PeriodParameters.EndDate.Date;
                TimeSpan span = endDate - startDate;
                for (int i = 0; i <= span.Days; i++)
                {
                    Reservation reservation = reservationList.Where(r => r.Slot.Date == startDate.AddDays(i)).SingleOrDefault();
 
                    reservationObservableCollection.Add(reservation);
                }
            });
            query.ExecuteAsync();
            return reservationObservableCollection;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value;
        }
    }

As you can see, the row IValueConverter will load for each room row the corresonding reservation data in an asynchronous way. So, if you have a lot of reservation data, the loading can take a few seconds, but it will never block the screen.

The LightSwitch screen.

The LightSwitch screen containing the reservation control is very light and contains only the code for showing the modal form for editing the current reservation:

public partial class SimpleCustomControl
   {
       partial void EditDetails_Execute()
       {
           this.OpenModalWindow("CurrentReservation");
       }
   }

Sample Project

You can find here  the sample project: RoomReservation. It contains also functionality to generate some working test data.

Enjoy !

Many thanks to Lifeng for the help on the LightSwitch forum.