Saturday, April 16, 2011

d:DesignInstance in WPF 4

Только что посмотрел сессию Deep Dive MVVM с прошедшей недавно конференции MIX11. Посмотрите видео. Гарантирую, что не пожалеете потраченного времени.

Я же сейчас хочу остановиться только на одном моменте. Когда-то я уже писал про то как можно организовать Blendability для ваших View. А вот из сегодняшнего видео узнал, что оказывается в WPF4 есть возможность организовать все несколько проще.

Я напомню, что я предлагал создавать отдельный класс ViewModel’и для design time. Привязка такой ViewModel’и ко View выглядела вот так:

<UserControl
    x:Class="ViewModelLocatorSample.EditTagView"
    ...
    d:DataContext="{Binding Source={StaticResource editVm}}">
    <UserControl.Resources>
        <vm:EditTagViewModelDesign x:Key="editVm" />
    </UserControl.Resources>
    ...
</UserControl>

Однако вот как можно сделать тоже самое проще:

<UserControl
    x:Class="ViewModelLocatorSample.EditTagView"
    ...
    d:DataContext="{d:DesignInstance Type=vm:EditTagViewModel,
        IsDesignTimeCreatable=True}">

Дизайнер сам создаст экземпляр ViewModel’и подтянет из нее данные. Тут есть только один маленький нюанс. Теперь наша единственная ViewModel’ь должна иметь конструктор без параметров (иначе дизайнер не сможет ее создать) и уметь инициализировать себя данными-заглушками. Примерно следующим образом:

internal sealed class EditTagViewModel
{
    public EditTagViewModel()
    {
        if (IsInDesignMode)
        {
            Tag = new TagInfo
            {
                Name = "DummyTag",
                Score = 99
            };
        }
    }
    ...
}

Исходные коды всех примеров показанных в видео, вы можете скачать здесь.

Thursday, March 17, 2011

WPF DataGrid tips: Forwarding the DataContext to columns

В прошлом посте я показал как можно изменить поведение DataGrid’а с помощью кастомного behavior’а. Давайте, взяв за основу созданное там приложениe, решим еще одну задачку.

Сейчас название тега в гриде представлено как простейший DataGridTextColumn. Т.е. в режиме редактирования мы получаем TextBox и можем изменять название как нам угодно. Давайте ограничим свободу ввода тегов, разрешив пользователю только выбирать теги из некоторого списка допустимых значений.

Для начала во ViewModel’ь добавим коллекцию AllTags, которая будет содержать все допустимые названия тегов.

public MainViewModel(IDataProvider provider)
{
    Tags = new ObservableCollection<TagInfo>(provider.Tags);
    AllTags = new ObservableCollection<string>(Tags.Select(t => t.Name));
}

public ObservableCollection<TagInfo> Tags
{
    get;
    private set;
}

public ObservableCollection<string> AllTags
{
    get;
    private set;
}

Теперь заменим колонку в гриде:

<DataGridComboBoxColumn
    Header="Tag"
    ItemsSource="{Binding AllTags}"
    SelectedValueBinding="{Binding Name}" />

Запускаем приложение и видим вот такую картину.

emptyCombo

Что случилось? Где потерялись названия тегов? Давайте разберемся. Для начала, как всегда в ситуациях, когда Binding ведет себя не так как мы ожидали, смотрим в окно Output, где находим ошибку:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=AllTags; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=29758132); target property is 'ItemsSource' (type 'IEnumerable')

Проблема в том, что колонки грида не принадлежат его визуальному дереву. Соответственно они не могут наследовать от грида никакие свойства. Поэтому не смотря на то, что мы устанавливаем DataContext для самого грида, DataContext для любого из столбцов будет null.

Решение состоит с том, чтобы при изменении  DataContext’а для грида, руками устанавливать DataContext для каждого столбца. Т.к. тип DataGridColumn (и интересующие нас производные от него) не содержит свойства DataContext, то мы воспользуемся DataContext’ом из типа FrameworkElement. Новую функциональность мы не будем добавлять в codebehind, а определим как отдельный behavior.

public sealed class PushDataContextToColumnsBehavior : Behavior<DataGrid>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.DataContextChanged += DataGrid_DataContextChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.DataContextChanged -= DataGrid_DataContextChanged;
    }

    private void DataGrid_DataContextChanged(
        object sender, DependencyPropertyChangedEventArgs e)
    {
        foreach (var column in AssociatedObject.Columns)
        {
            column.SetValue(
                FrameworkElement.DataContextProperty,
                e.NewValue);
        }
    }
}

Теперь наш грид будет выглядеть вот так:

<DataGrid
    ItemsSource="{Binding Tags}"
    AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridComboBoxColumn
            Header="Tag"
            ItemsSource="{Binding (FrameworkElement.DataContext).AllTags,
                RelativeSource={RelativeSource Self}}"
            SelectedValueBinding="{Binding Name}" />
        <DataGridTextColumn Header="Score" Binding="{Binding Score}" />
    </DataGrid.Columns>
    <i:Interaction.Behaviors>
        <bhvr:PerCellCommitBehavior />
        <bhvr:PushDataContextToColumnsBehavior />
    </i:Interaction.Behaviors>
</DataGrid>

Запустив приложение мы увидим то, что и ожидали

combo

Как обычно, проект можно скачать отсюда. А вот здесь можно найти альтернативный вариант решения.

Sunday, March 13, 2011

WPF DataGrid tips: Per cell commiting behavior

Несколько месяцев назад проект над которым я тогда работал благополучно переехал на .Net 4.0. Одним из результатов этого перехода было то, что мы решили отказаться от грида Infragistics в пользу WPF DataGrid. Производительность нового грида оказалась выше всяких похвал. Там где Infragistics надолго задумывался, WPF DataGrid стал отображать данные буквально без задержки. Сам процесс замены гридов прошел достаточно безболезненно, однако были несколько моментов, над которыми пришлось поломать голову. В этом и нескольких последующих постах я хочу привести проблемы с которыми я столкнулся и решения на которых остановился.

Итак, проблема первая. При редактировании ячеек WPF DataGrid’a данные в исходной коллекции обновляются не после того как вы закончите редактировать непосредственно ячейку, а только после того как закончите редактировать всю строку целиком.

Для того чтобы получить нужное поведение грида оказалось достаточным в обработчике события CellEditEnding заставлять грид сохранять все имеющиеся изменения.

private void Grid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
    if (_manualEditCommit)
        return;

    if (e.EditAction != DataGridEditAction.Commit)
        return;

    _manualEditCommit = true;
    var grid = (DataGrid)sender;
    grid.CommitEdit(DataGridEditingUnit.Row, true);
    _manualEditCommit = false;
}

Однако тут возник другой вопрос: где именно это реализовать? В code behind? Однозначно нет. Сделать производный от грида PerCellCommitDataGrid? Тоже не то.

Мне нравятся два варианта. Вариант первый: Сделать Attached property PerCellCommit, которое будет подписывать нужный обработчик на событие CellEditEnding.

public static class DataGridHelper
{
    public static readonly DependencyProperty PerCellCommitProperty =
        DependencyProperty.RegisterAttached(
            "PerCellCommit",
            typeof(bool),
            typeof(DataGridHelper),
            new PropertyMetadata(false, PerCellCommitChanged));

    public static bool GetPerCellCommit(DependencyObject obj)
    {
        return (bool)obj.GetValue(PerCellCommitProperty);
    }

    public static void SetPerCellCommit(DependencyObject obj, bool value)
    {
        obj.SetValue(PerCellCommitProperty, value);
    }

    public static void PerCellCommitChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var grid = obj as DataGrid;
        if (grid == null)
            return;

        if ((bool)e.NewValue)
        {
            grid.CellEditEnding += Grid_CellEditEnding;
        }
        else
        {
            grid.CellEditEnding -= Grid_CellEditEnding;
        }
    }

    private static bool _isManualEditCommit;

    private static void Grid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        if (_isManualEditCommit)
            return;

        if (e.EditAction != DataGridEditAction.Commit)
            return;

        _isManualEditCommit = true;
        var grid = (DataGrid) sender;
        grid.CommitEdit(DataGridEditingUnit.Row, true);
        _isManualEditCommit = false;
    }
}

В этом случае в XAML новую функциональность гриду можно подключить следующим образом:

<DataGrid
    ItemsSource="{Binding Tags}"
    presentation:DataGridHelper.PerCellCommit="True">
    <DataGrid.Columns>
        ...
    </DataGrid.Columns>
</DataGrid>

Вариант второй: Реализовать для грида кастомный behavior. Что такое behavior’ы и чем они полезны рекомендую почитать вот тут.

public class DataGridPerCellCommitBehavior : Behavior<DataGrid>
{
    private static bool _manualEditCommit;

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.CellEditEnding += Grid_CellEditEnding;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.CellEditEnding -= Grid_CellEditEnding;
    }

    private void Grid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        if (_manualEditCommit)
            return;

        if (e.EditAction != DataGridEditAction.Commit)
            return;

        _manualEditCommit = true;
        var grid = (DataGrid)sender;
        grid.CommitEdit(DataGridEditingUnit.Row, true);
        _manualEditCommit = false;
    }
}

В этом случае грид будет выглядеть примерно так:

<DataGrid
    ItemsSource="{Binding Tags}">
    <DataGrid.Columns>
        ...
    </DataGrid.Columns>
    <i:Interaction.Behaviors>
        <bhvr:DataGridPerCellCommitBehavior />
    </i:Interaction.Behaviors>
</DataGrid>

Исходники демки с реализованным behavior’ом можно скачать тут.

Friday, February 18, 2011

Git Rocks!

До недавнего времени у меня был опыт использования только так называемых централизованных систем контроля версий (CVCS). Сперва это был Visual SVN для личных проектов, потом Microsoft TFS на работе. И вот теперь (о, ужас!) Perforce, опять же на работе.

Perforce стоит упомянуть отдельно. За свои тормоза и отвратительную интеграцию с Visual Studio он вполне заслуживает вот таких лестных отзывов. Как говорится, сделано чужими для хищников.

Но вернемся к Git. Я решил совместить полезное с полезным и во-первых разобраться что же такое распределенные системы контроля версий (DVСS), а во-вторых размещать демки для постов этого блога на каком-нибудь публичном хостинге, а не в архивах на SkyDrive. Почему именно Git, а не, скажем, Mercurial? Не знаю. Потому что Git :)

Я не буду сейчас рассказывать о том,  что же такое Git, чем он отличается от аналогов, описывать его команды или приводить сценарии его использования. Все это вы сможете найти в книгах и документации, ссылки на которые я приведу ниже. Этот пост скорее о моем очень позитивном впечатлении от изучения и использования этой системы.

А теперь обещанные ссылки:

  1. скачать Git можно вот отсюда git-scm.com

  2. На том же сайте есть отличный раздел Documentation, где вы сможете найти туториалы, книги и даже видео материалы.

  3. Из книг я выбрал вот эту Pro Git (видимо Pro в названии подкупило). Книга доступна онлайн, читается легко, содержит множество примеров. Рекомендую.

  4. Вот здесь Git Reference вы найдете описание всех команд Git и их параметров. Кроме того для каждой команды есть ссылка на соответствующую главу из  Pro Git, а это очень удобно.

  5. github самый крупный онлайн сервис, где вы можете размещать свои Git репозитории.

  6. Для пользователей Windows, думаю, будут полезны GitExtensions, которые предоставляют удобную графическую оболочку для Git.

  7. И, наконец, плагин для Visual Studio, который позволяет работать с Git, прямо из Solution Explorer.



Monday, January 31, 2011

Про подкасты...

Сегодня утром по дороге на работу, слушая один из последних выпусков .NET Rocks!, я поймал себя на мысли, что за последние несколько месяцев подкасты стали для меня важным источником информации, уступающим, пожалуй, только книгам и блогам. Поэтому я решил написть о подкастах, которые слушаю. Возможно, вас они тоже заинтересуют.

dnr

Начать этот список хочу с, на мой взгляд, самого яркого, информативного и живого англоязычного подкаста, посвященного платформе .Net. Новые выпуски .Net Rocks! появляются каждую неделю. Самым ценным контентом для меня являются интервью с интересными гостями, т.к. всегда можно узнать какие-то нюансы или услышать оригинальное объяснение каких-то технологий, что называется из первых рук.

hansel

Hanselminutes еще один отличный англоязычный подкаст, посвященный .Net и связанным с ней технологиям.

 

connectedShow

Connected Show - менее регулярный, но не менее интереcный подкаст посвященный технологиям Microsoft.

distrSystems

Distributed podcast Новый подкаст, посвященный разработке распределенных систем, облачным вычислениям. Особое внимание уделяется таким понятиям как Event Based Architecture, Domain Driven Design, Command-Query Responsibility Segregation.

spbAltNet

Подкаст Петербургской группы Alt.Net. Ребята записали 24 выпуска и вот уже пару месяцев молчат. Надеюсь, что они продолжат записываться, т.к содержание у них было очень качественное. Рекомендую послушать то что есть,

solo

Соло на .Net Новый подкаст Дмитрия Нестерука, посвященный (вы не поверите) созданию приложений на платформе .Net

 

А теперь немного офтопика, а точнее подкастов, которые я слушаю, но которые не связаны напрямую с программированием  и/или .Net

radiot

Отличный веселый еженедельный новостной подкаст на все околоайтишные темы. Если вы гик, то Радио Т это подкаст для вас.

2giga

2 Гига – еще один интересный еженедельный подкаст о новостях в мире IT.


f56Ну и наконец совсем уж офтоп. Русскоязычный подкаст о фотографии f 5.6



Вот такая получилась у меня подборочка. А что слушаете вы?