Статьи

Использование Coding4Fun ChatBubble и ChatBubbleTextBox в приложении Windows Phone

Клинт Руткас недавно зарегистрировал новую версию Coding4Fun Toolkit для Windows Phone, в которой представлены элементы управления ChatBubble и ChatBubbleTextBox . С помощью вышеупомянутых дополнений можно копировать пользовательский интерфейс Windows Phone Messaging.

ChatBubble — это контейнер, который может использоваться для отображения любого вида визуального контента в форме чата. ChatBubbleTextBox используется для приема пользовательского ввода. Вместо того, чтобы давать вам скучный пример со списком свойств элементов управления, давайте создадим симуляцию интерфейса обмена сообщениями.

Вот сетка, разделенная на три строки: верхняя часть предназначена для имени контакта, нижняя — там, где вводится текст для отправки, а средняя — там, где отображаются сообщения.

<phone:PhoneApplicationPage 
x:Class="RingtoneFeeder.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
xmlns:cc="clr-namespace:Coding4Fun.Phone.Controls;assembly=Coding4Fun.Phone.Controls" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="800" d:DesignWidth="480">

<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="550"></RowDefinition>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="PageTitle" Text="den d." Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>

<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<cc:ChatBubble ChatBubbleDirection="LowerRight">
Test
</cc:ChatBubble>
</StackPanel>

<StackPanel Margin="12,0,12,12" Grid.Row="2" Orientation="Horizontal">
<cc:ChatBubbleTextBox Margin="0,6,0,0" ChatBubbleDirection="LowerRight" TextWrapping="Wrap" Width="340"></cc:ChatBubbleTextBox>
<Button Content="Send"></Button>
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>

В эмуляторе это выглядит так:

Это довольно статичный интерфейс — здесь не так много действий. Вы можете видеть, что элемент управления ChatBubble имеет свойство ChatBubbleDirection , которое определяет, где отображается обработчик чата (маленький треугольник в углу пузыря). То же самое относится и к ChatBubbleTextBox . В настоящее время я использую один экземпляр ChatBubble , для которого в качестве содержимого задан текст. Однако может появиться больше сообщений чата, поэтому более разумно использовать ListBox с пользовательским шаблоном DataTemplate, который является элементом управления ChatBubble .

Как вы, вероятно, знаете, текстовое сообщение, отправляемое через Messaging Hub на Windows Phone, также содержит дату его отправки, поэтому давайте возьмем элемент управления Grid в ChatBubble, разделив его на две строки — одну для текстового сообщения и другую для Дата:

<cc:ChatBubble  Margin="0,0,0,20" ChatBubbleDirection="LowerRight">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
        </Grid.RowDefinitions>
        
        <TextBlock Text="Testing the capabilities." TextWrapping="Wrap" Width="430"></TextBlock>
        
        <TextBlock Grid.Row="1" HorizontalAlignment="Right" Text="12/12/12"></TextBlock>
    </Grid>
</cc:ChatBubble>

Таким образом, я легко получаю следующий вид:

Но для Сообщения нет модели, поэтому давайте создадим ее. Добавьте класс в проект и назовите его Message.cs . В этом классе вам нужны два строковых свойства — Text и SendingDate .

public class Message
{
    public string Text { get; set; }
    public string SendingDate { get; set; }
}

Возвращаясь к модели данных XAML, давайте свяжем элементы управления TextBlock со свойствами в этом классе Message :

<Grid Width="456">
<cc:ChatBubble Width="340" Margin="0,0,0,20">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>

<TextBlock Text="{Binding Text}" TextWrapping="Wrap" Width="430"></TextBlock>

<TextBlock Grid.Row="1" HorizontalAlignment="Right" Text="{Binding SendingDate}"></TextBlock>
</Grid>
</cc:ChatBubble>
</Grid>

Эта модель данных должна быть интегрирована в ListBox как ItemTemplate :

<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="456">
<cc:ChatBubble Width="340" Margin="0,0,0,20">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>

<TextBlock Text="{Binding Text}" TextWrapping="Wrap" Width="430"></TextBlock>

<TextBlock Grid.Row="1" HorizontalAlignment="Right" Text="{Binding SendingDate}"></TextBlock>
</Grid>
</cc:ChatBubble>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Однако приведенный выше список будет пустым, поскольку для ItemsSource нет привязки. Чтобы исправить это, я создал центральный класс связывания, который следует шаблону, описанному в этой статье .

Обратите внимание, что я использую ObservableCollection <Message> для удобной повторной привязки при обновлении коллекции.

using System;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace RingtoneFeeder
{
    public class Binder : INotifyPropertyChanged
    {
        static Binder instance = null;
        static readonly object padlock = new object();

        public Binder()
        {
            Messages = new ObservableCollection<Message>();
        }

        public static Binder Instance
        {
            get
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        instance = new Binder();
                    }
                    return instance;
                }
            }
        }

        private ObservableCollection<Message> messages;
        public ObservableCollection<Message> Messages
        {
            get
            {
                return messages;
            }
            set
            {
                if (messages != value)
                {
                    messages = value;
                    NotifyPropertyChanged("Messages");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                Deployment.Current.Dispatcher.BeginInvoke(() => { PropertyChanged(this, new PropertyChangedEventArgs(info)); });
            }
        }
    }
}

Этот класс предоставляется через ресурс в App.xaml :

<Application 
    x:Class="RingtoneFeeder.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"       
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:local="clr-namespace:RingtoneFeeder">

    <Application.Resources>
        <local:Binder x:Key="Binder"></local:Binder>
    </Application.Resources>

    <Application.ApplicationLifetimeObjects>
        <shell:PhoneApplicationService 
            Launching="Application_Launching" Closing="Application_Closing" 
            Activated="Application_Activated" Deactivated="Application_Deactivated"/>
    </Application.ApplicationLifetimeObjects>
</Application>

Теперь я могу привязать ListBox к основной коллекции сообщений:

<ListBox ItemsSource="{Binding Path=Instance.Messages,Source={StaticResource Binder}}">

Теперь давайте удостоверимся, что когда пользователь нажимает кнопку « Отправить» , сообщение добавляется в коллекцию. Я назвал ChatBubbleTextBox как txtMessage и кнопку btnSend .

private void btnSend_Click(object sender, RoutedEventArgs e)
{
    if (!string.IsNullOrEmpty(txtMessage.Text))
    {
        Message message = new Message()
        {
            Text = txtMessage.Text,
            SendingDate = DateTime.Now.ToShortDateString()
        };

        Binder.Instance.Messages.Add(message);
        txtMessage.Text = string.Empty;
    }
}

Посмотрим, что мы получим сейчас:

Однако есть одна проблема. Если вы посмотрите на настоящий Messaging Hub, вы заметите, что как направление обработчика чата (опять же, этот маленький треугольник в углу), так и оттенок основного цвета переключаются между сообщениями, отправленными владельцем телефона, и сообщениями, отправленными собеседник.

В моей симуляции, давайте предположим, что каждое второе сообщение на самом деле является ответом. Мне нужно будет изменить свойства ChatBubbleDirection, Alignment и Opacity в зависимости от отправителя. Это легко сделать, основываясь на индексе сообщения в коллекции, и я создал собственный конвертер, чтобы обнаружить именно это:

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    int ival = Binder.Instance.Messages.IndexOf((Message)value);

    // Checks the modulus, whether the index is odd or even
    if (ival % 2 == 0)
    {
        if (parameter == null)
            return 1; // no parameter - return opacity
        else
        {
            if (parameter.ToString() == "direction") // return chat "triangle" direction
            {
                return ChatBubbleDirection.LowerRight;
            }
            else  // return aligment
            {
                return HorizontalAlignment.Right;
            }
        }
    }
    else
    {
        if (parameter == null)
            return .8;
        else
        {
            if (parameter.ToString() == "direction")
            {
                return ChatBubbleDirection.UpperLeft;
            }
            else
            {
                return HorizontalAlignment.Left;
            }
        }
    }
}

Привязка для самого ChatBubble выглядит следующим образом:

<cc:ChatBubble Width="340" HorizontalAlignment="{Binding Converter={StaticResource MType},ConverterParameter=align}"  Opacity="{Binding Converter={StaticResource MType}}" ChatBubbleDirection="{Binding Converter={StaticResource MType},ConverterParameter=direction}" Margin="0,0,0,20">

Вот как выглядит пользовательский интерфейс, когда сообщения хранятся:

Вот оно! Если вам нужно загрузить пример проекта, НАЖМИТЕ ЗДЕСЬ .