Coproject - a RIA Caliburn.Micro demo, part 11

In this part, we will synchronize list after saving data and add some icons. You may continue on your work from older parts, or download updated code from Coproject site.

Event aggregator

Usually, if you want a component of your application to notify other components about an event that occurred, you use standard .NET events. The problem with events is that you always need to access the publisher of an event from the subscriber. What’s more, you also need to unsubscribe from the event. That might be tricky in a complicated application or when the same type of event may come from more sources (for example if you would implement user events logging via events and logger subscribed to these events).

Here comes (again) Caliburn.Micro and its implementation of Event Aggregator. Consider EventAggregator as a component that you subscribe to all kinds of events or that you use to publish an event.

In Coproject, we will use EventAggregator to notify ToDoItemsViewModel that ToDoItem has been changed and thus it should reload data.

First of all, we must create an object that will represent the event. In Coproject client project, create new folder Events and put a new class called ToDoItemUpdatedEvent into it:

public class ToDoItemUpdatedEvent
{
        public int ToDoItemID { get; set; }
        public ToDoItemUpdatedEvent(int toDoItemID)
        {
                ToDoItemID = toDoItemID;
        }
}

Open ToDoItemViewModel and let MEF import an EventAggregator instance:

[Import]
public IEventAggregator EventAggregator { get; set; }

Then update Save as follows:

public IEnumerable<IResult> Save()
{
        (Item as IEditableObject).EndEdit();
        IsReadOnly = true;
        #region Fix DataForm bug
        IsReadOnly = false; IsReadOnly = true;
        #endregion

        yield return new SaveDataResult(_context);

        EventAggregator.Publish(new ToDoItemUpdatedEvent(Item.ToDoItemID));
}

So, after (remember coroutines and yield return) a ToDoItem is saved, we publish event notifying about it. We do not care who subscribes to this event, this is not our business here.

Now, let’s write the other end – an event subscriber. Open ToDoListsViewModel and edit the constructor:

[ImportingConstructor]
public ToDoListsViewModel(IEventAggregator eventAggregator)
{
        DisplayName = "To Do";
        Description = "To-do lists";

        eventAggregator.Subscribe(this);
}

Now, event aggregator will know that it should notify ToDoListsViewModel about events. But what events? It depends on what IHandle<T> interfaces the subscriber implements. Let the view model implement IHandle<ToDoItemUpdatedEvent> and add this implementation:

public void Handle(ToDoItemUpdatedEvent message)
{
        LoadData().ToSequential().Execute(null);
}

Remember that LoadData returns iEnumerable<IResult> so we have to enumerate it to ‘run’ it. Therefore, we wrap it into a SequentialResult and then execute this single result.

The only problem is that LoadData requires a parameter – filter. And this value is unknown to the handler. Fortunately, we can change the source Filter textbox binding from function parameter to property. Add this property to ToDoListsViewMode:

public string Filter { get; set; }

And the remove parameter ‘filter’ from LoadData (take the filter value from Filter).

Build the application – it should run as before, but after saving of a ToDoItem, the list should refresh automatically.

Icons

Let’s give Coproject a little more style – change text buttons to icon buttons.

Open the zip file attached to this post and extract the five images to Assets/Icons. These icons are from Axialis.

Then, open ToDoListsView and edit the LoadData button:

<Button x:Name="LoadData" Grid.Column="1" ToolTipService.ToolTip="Search">
        <Button.Content>
                <Image Source="/Coproject;component/Assets/Icons/Search.png" Height="20" />
        </Button.Content>
</Button>

Well, this works nicely but let’s make it a little more shorter since we will probably use these image buttons quite frequently in our application. To do this, we will create a custom control called ImageButton. Create a new folder Controls and add this control to it:

public class ImageButton : Button
{
        public string ImageName
        {
                get { return (string)GetValue(ImageNameProperty); }
                set { SetValue(ImageNameProperty, value); }
        }

        public static readonly DependencyProperty ImageNameProperty =
                DependencyProperty.Register("ImageName", typeof(string), typeof(ImageButton), new PropertyMetadata(OnImageNamePropertyChanged));

        public static void OnImageNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
                ImageButton button = d as ImageButton;
                string newValue = e.NewValue as string;

                if (newValue.IsNullOrWhiteSpace())
                {
                        button.Content = null;
                        return;
                }

                newValue = "/Coproject;component/Assets/Icons/{0}.png".FormatWith(newValue);
                ImageSource image = new BitmapImage(new Uri(newValue, UriKind.Relative));
                button.Content = new Image { Source = image };
        }
}

As you can see, we inherited original Button and added a property to it. The reason that ImageName is implemented as a dependency property is to enable it for binding, styling, etc. When the property is changed a new Image is inserted to the button. It would be great to add this image into ContentTemplate by style and just bind its Source property to ImageName, but since in template binding, you cannot use any converters, we would have to set ImageName in the raw form (/Coproject;component/Assets/Icons/Search.png) and that is not what we want. So that is why I’ve chosen this way. Now open Assets/Cosmopolitan/Custom.xaml and add this namespace to it:

xmlns:local="clr-namespace:Coproject.Controls"

And then add this to the end of the file, just above </ResourceDictionary>:

<Style TargetType="local:ImageButton" BasedOn="{StaticResource DefaultButtonStyle}">
        <Setter Property="Height" Value="32" />
</Style>

We are done – get back to ToDoListsView, register namespace for ImageButton as in Custom.xaml and update LoadData button:

<local:ImageButton x:Name="LoadData" Grid.Column="1" ImageName="Search" ToolTipService.ToolTip="Search" />

Update buttons in Toolbar view in the same manner:

<local:ImageButton x:Name="Edit" ImageName="Pen" ToolTipService.ToolTip="Edit" Margin="0,0,5,0" />
<local:ImageButton x:Name="Cancel" ImageName="Undo" ToolTipService.ToolTip="Cancel" Margin="0,0,5,0" />
<local:ImageButton x:Name="Save" ImageName="Save" ToolTipService.ToolTip="Save" Margin="0,0,5,0" />
<local:ImageButton x:Name="TryClose" ImageName="Close" ToolTipService.ToolTip="Close" />

That is all. I hope you like the new look!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值