Расширение для представлений, которое добавляет текст или кнопку на панель фильтрации
Расширение для представлений, которое добавляет текст или кнопку на панель фильтрации¶
Допустим, требуется написать расширение, которое в область представления добавляет текст (сколько % дискового места занято на некотором ресурсе) и кнопку. По аналогии можно добавлять и заменять любое содержимое, отображаемое в любой части в области представлений, например, в панели фильтрации или в области таблицы.
Для этого используются расширения IWorkplaceViewComponentExtension
вместе со связкой ViewModel+View
для отображаемого содержимого.
В сборке Tessa.Extensions.Client
создадим папку Views
и добавим в неё класс расширения TotalSizeWorkplaceComponentExtension
.
using Tessa.Platform.Runtime;
using Tessa.UI.Views;
using Tessa.UI.Views.Content;
namespace Tessa.Extensions.Client.Views
{
public sealed class TotalSizeWorkplaceComponentExtension : IWorkplaceViewComponentExtension
{
public TotalSizeWorkplaceComponentExtension(ISession session)
{
// любые сервисы можно получить из Unity здесь
this.session = session;
}
private readonly ISession session;
public void Initialize(IWorkplaceViewComponent model)
{
// добавляем ViewModel с некоторым уникальным именем в область тулбара ContentPlaceAreas.ToolbarPlaces
model.ContentFactories.Add(
"TotalSizeToolbar",
component =>
new TotalSizeWorkplaceComponentViewModel(
this.session,
component,
ContentPlaceAreas.ToolbarPlaces,
ordering: int.MinValue));
}
public void Initialized(IWorkplaceViewComponent model)
{
}
public void Clone(IWorkplaceViewComponent source, IWorkplaceViewComponent cloned, ICloneableContext context)
{
}
}
}
Для расширения пишется регистратор в классе Registrator
, который выполняет регистрацию в специальном объекте IWorkplaceExtensionRegistry
.
using Microsoft.Practices.Unity;
using Tessa.UI.Views.Extensions;
namespace Tessa.Extensions.Client.Views
{
[Registrator]
public sealed class Registrator : RegistratorBase
{
public override void FinalizeRegistration()
{
this.UnityContainer
.Resolve<IWorkplaceExtensionRegistry>()
.Register(typeof(TotalSizeWorkplaceComponentExtension))
;
}
}
}
Модель представления TotalSizeWorkplaceComponentViewModel
получает через конструктор как зависимости, необходимые для базового класса BaseContentItem
, так и произвольные зависимости из Unity (здесь это ISession
).
Далее в представлении задаётся метод Refresh
, определяющий, что происходит при очередном обновлении данных в представлении. В нашей реализации обновляется свойство Text
с некоторыми текстом, который зависит от данных представления.
Метод ButtonAction
(совместно с командой ButtonCommand
) определяет некоторую бизнес-логику при нажатии на кнопку, которая размещается рядом с прочими системными кнопками.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Tessa.Platform.Runtime;
using Tessa.Platform.Storage;
using Tessa.UI;
using Tessa.UI.Views;
using Tessa.UI.Views.Content;
namespace Tessa.Extensions.Client.Views
{
public sealed class TotalSizeWorkplaceComponentViewModel : BaseContentItem
{
public TotalSizeWorkplaceComponentViewModel(
ISession session,
IWorkplaceViewComponent component,
IEnumerable<IPlaceArea> placeAreas,
Func<IPlaceArea, DataTemplate> dataTemplateFunc = null,
int ordering = PlacementOrdering.Middle)
: base(placeAreas, dataTemplateFunc, ordering)
{
this.session = session;
this.component = component;
this.component.PropertyChanged += this.ComponentPropertyChanged;
this.buttonCommand = new DelegateCommand(this.ButtonAction);
}
private readonly ISession session;
private readonly IWorkplaceViewComponent component;
private void Refresh()
{
IEnumerable<IDictionary<string, object>> data = this.component.Data;
if (data == null || this.component.IsDataLoading)
{
this.Text = null;
return;
}
const int maxSizeInGb = 30; // значение можно получить из настроек
// пусть текущий размер хранится в представлении в колонке TotalSize для первой возвращаемой строки
IDictionary<string, object> firstRow = data.FirstOrDefault();
double totalSizeInMb = firstRow != null ? firstRow.Get<double>("TotalSize") : 0.0;
// в гигабайтах цифра будет меньше на 1000
double totalSizeInGb = totalSizeInMb / 1000.0;
int percent = Math.Min((int)(totalSizeInMb / 10.0 / maxSizeInGb), 100);
// используем культуру для форматирования чисел с плавающей запятой из настроек текущего сотрудника
string totalSizeText = totalSizeInGb.ToString("F1", this.session.ClientCulture).TrimEnd('0', '.', ',');
this.Text = string.Format("Занято {0}% {1} Гб из {2}", percent, totalSizeText, maxSizeInGb);
}
private void ComponentPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Data" || e.PropertyName == "IsDataLoading")
{
this.Refresh();
}
}
private void ButtonAction(object parameter)
{
TessaDialog.ShowMessage("Имя пользователя: " + this.session.User.Name);
}
private string text;
public string Text
{
get { return this.text; }
set
{
if (this.text != value)
{
this.text = value;
this.OnPropertyChanged("Text");
}
}
}
private readonly ICommand buttonCommand;
public ICommand ButtonCommand
{
get { return this.buttonCommand; }
}
}
}
Теперь добавим View
для созданной ViewModel
, разместим её в UserControl
с именем TotalSizeWorkplaceComponentView.xaml
.
<UserControl x:Class="Tessa.Extensions.Client.Views.TotalSizeWorkplaceComponentView"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tuic="clr-namespace:Tessa.UI.Controls;assembly=Tessa.UI"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,0"
VerticalAlignment="Center"
FontSize="16"
Foreground="Black"
Text="{Binding Text}" />
<Button Height="32"
Margin="5,3"
Command="{Binding ButtonCommand, Mode=OneTime}"
Style="{StaticResource BorderlessButton}"
ToolTip="Нажмите на кнопку">
<tuic:AutoDisabledPath Data="{StaticResource Thin228}"
Fill="Black"
Stretch="Uniform" />
</Button>
</StackPanel>
</UserControl>
Связь между View
и ViewModel
определяется объектом DataTemplate
в ResourceDictionary
с именем ViewModels.xaml
, который уже располагается в папке Resources
для сборки Tessa.Extensions.Client
.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Tessa.Extensions.Client"
xmlns:views="clr-namespace:Tessa.Extensions.Client.Views">
<!-- Здесь располагаются DataTemplate для связи ViewModel и View -->
<DataTemplate DataType="{x:Type views:TotalSizeWorkplaceComponentViewModel}">
<views:TotalSizeWorkplaceComponentView />
</DataTemplate>
</ResourceDictionary>
Далее создадим представление TotalSizeView
, которое содержит простой запрос, возвращающий строку с единственной колонкой TotalSize
(случайное число от 100 до 30000). Вкладка “Метаинформация” в представлении будет пуста.
declare @min float = 100
declare @max float = 30000
select round(@min + rand() * (@max - @min - 1), 0) as TotalSize
Скомпилируйте расширения Tessa.Extensions.Client
, скопируйте их в папку Tessa Admin
и перезапустите его. В рабочем месте добавьте представление в дерево, и укажите расширение TotalSizeWorkplaceComponentExtension
.
В результате на вкладке “Просмотр” будет доступна надпись и кнопка, при нажатии на которую отображается имя текущего сотрудника. Надпись будет изменяться каждый раз, когда вы обновляете представление, за счёт функции rand()
в запросе SQL.
Теперь вы можете скопировать расширения в папку Tessa Client
и опубликовать приложение. Если вы корректно настроили роли для представления TotalSizeView
, то пользователи получат обновлённую версию приложения вместе с написанными расширениями.