您的位置:首页 > 运维架构

Coproject - a RIA Caliburn.Micro demo, part 8

2015-09-03 06:46 405 查看
In this part, we will create ToDoItem detail.


Context lifetime

The main question when considering lists and details is whether to share context among all screens or create a new context per screen. Both ways have their pros and cons and you will probably have to choose depending on the situation. But most likely, you will
use both of them depending on the scope of entities.


Shared context

In this case, you would probably create a service in the application and all screens would access it to get and save data. It is useful when some entities are used across the whole application.

+ You can pass entities across application

+ No need to synchronize data when an entity gets updated – all parts of the application use the same entity.

+ Lower memory consumption if entities are shared across application.

+ Centralized data access.

- Entities stay in memory until detached or context disposed. So if you use a shared context to load hundreds of entites (e.g, in a grid) and do not detach them, memory consumption will be a problem.

- Even unsaved state of entities affect other parts of application (sharing the same entity).

- Harder saving. If you save whole context, you might save currently edited entities of other screens.

- Harder to maintain, especially with current memory leaks in Silverlight.


Context per screen

Every screen creates its own context and loads and saves data on its own. This is probably better for scenarios with editing detail screens and large lists/grids.

+ Easier to see consequences of changing entity properties.

+ Memory management. You can easily dispose of context (and thus all entities it loaded).

+ Easier saving. If you save context, you don’t save currently edited entities of other screens.

- Need to pass IDs across application and every screen must load the entity itself.

- Duplicate entities might exist in the application.

- Need to synchronize changes throughout application when entity is updated.

- Possible concurrency errors while saving.


ToDoItemViewModel

In Coproject, we will use Context per screen for ToDoItem entity. So, let’s start. I hope you have done all the steps described so far. If you haven’t, just download source code from Coproject
Codeplex site. Note that source codes are branched for every tutorial part so you can start in any stage.

First of all, we should create an interface for ToDoItem detail. So, create interface ViewModels.Interfaces.IToDoItemEditor:
public interface IToDoItemEditor
{
        ToDoItem Item { get; }
        IEnumerable<IResult> Setup(int toDoItemID);
}


Then, add ToDoItemViewModel to ViewModels:
[Export(typeof(IToDoItemEditor))]
[PartCreationPo
12bd2
licy(CreationPolicy.NonShared)]
public class ToDoItemViewModel : Screen, IToDoItemEditor
{
        public ToDoItem Item { get; private set; }

        private CoprojectContext _context;

        public IEnumerable<IResult> Setup(int toDoItemID)
        {
                        _context = new CoprojectContext();

                        var dataResult = new LoadDataResult<ToDoItem>(
                                _context,
                                _context.GetToDoItemsQuery().Where(x => x.ToDoItemID == toDoItemID));
                        yield return dataResult;

                        Item = dataResult.Result.Entities.First();
                        NotifyOfPropertyChange(() => Item);
        }
}


As you can see, the detail screen expects ToDoItemID and loads the proper entity itself. We want to store the context because we will use it for saving. Since there will be single instance of view model for every ToDoItem detail opened, we need it to be of
a NonShared creation policy.

Now, let’s add the logic that will open the detail screen. There are ListBoxes in DataTeplates for every ToDoList in ToDoListsView so we could probably use the same trick as for modules to track the selected item, but as there are more ListBoxes, sharing SelectedItem
would lead at least to a binding error.  Moreover, I want to show how to use custom binding to events.

Firstly, open ToDoListsViewModel and change its base class from Screen to
Conductor<IToDoItemEditor>.Collection.OneActive


and add the following function to it:
public IEnumerable<IResult> OpenItemDetail(ToDoItem item)
{
        var editor = Items.FirstOrDefault(x => x.Item.ToDoItemID == item.ToDoItemID);
        if (editor == null)
        {
                editor = IoC.Get<IToDoItemEditor>();
                yield return editor.Setup(item.ToDoItemID).ToSequential();
        }

        ActivateItem(editor);
        yield break;
}


Finally, we need to execute this function in appropriate time. Open ToDoListsView and add new xmlns definition to the top of the file:
xmlns:cal="http://www.caliburnproject.org"


Now, we want to attach the OpenItemDetail function to the SelectionChanged event of the ListBox bound to ToDoItems. It the most fluent way, it would be done like this:
<ListBox ItemsSource="{Binding ToDoItems}"
                        cal:Message.Attach="[Event SelectionChanged] = [Action OpenItemDetail($this.SelectedItem)]" ...


But since SelectionChanged is the default event of ListBox, Action is the default behavior and SelectedItem is the default property of ListBox for binding, we can use this shortcut with the same result:
<ListBox ItemsSource="{Binding ToDoItems}" cal:Message.Attach="OpenItemDetail($this)"


Note the argument $this. This is a special C.M argument name. Other special words are $datacontext and $eventargs. Their meaning is exactly what you would expect from their names. You can read more about C.M actions here.

We also must add a place for the detail to be displayed in. So add this control below the ScrollViewer containing the Lists ItemsControl:
<ContentControl x:Name="ActiveItem" Grid.Column="1" Grid.Row="1" Margin="10,0,0,0"
                                HorizontalContentAlignment="Stretch" />


ToDoItemView

Next, we have to create the respective view. So add a new Silvelright User Control named ToDoItemView to Views. Add these two namespaces to it:
xmlns:dataForm="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"


Then add this DataForm to LayoutGrid:
<dataForm:DataForm CurrentItem="{Binding Item}" HorizontalAlignment="Stretch">
        <StackPanel>
                <dataForm:DataField PropertyPath="DueDate">
                        <controls:DatePicker SelectedDate="{Binding DueDate,Mode=TwoWay}" />
                </dataForm:DataField>
                <dataForm:DataField PropertyPath="Content">
                        <TextBox Text="{Binding Content,Mode=TwoWay}" TextWrapping="Wrap" Height="auto" />
                </dataForm:DataField>
                <dataForm:DataField PropertyPath="User">
                        <TextBlock Text="{Binding User.LastName}" />
                </dataForm:DataField>
        </StackPanel>
</dataForm:DataForm>


If you run the application, you will see this:




DataFields automatically added labels to proper places. You should note that there are no DataForm buttons. The reason is that in Assets/Custom.xaml, we have a default style that disables it. DataForm is still kind of a buggy control so I want to use as little
of its features as possible.

Let me mention four DataForm bugs we could encounter in Coproject:
Even though Commit and Cancel buttons are disabled, they sometimes appear when changing edit mode.
When switching between DataForms, it sometimes hide the first label in it (DueDate).
When setting ReadOnly mode after editing a TextBox inside the DataForm, controls inside the DataForm are still enabled.
Switching to edit mode with BusyIndicator on the same page causes the indicator to constantly use 100% CPU.

All of them, except for the second can be avoided / fixed.

You probably noticed that User is still empty. The cause is that this nested entity is not included in ToDoItem. So open Services/CoProjectService.cs in the server project, find GetToDoItems() and edit it as follows:
return this.ObjectContext.ToDoItems.Include("User");


Build and run the application. Detail should be complete:


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: