views:

208

answers:

1

I've been given the task to create a simple Silverlight chat box for two people. My control must adhere to the following requirements

  1. Scrollable
  2. Text must wrap if it's too long
  3. When a new item / message is added it must scroll that item into view

Now I've successfully made a usercontrol to meet these requirements, but I've run into a possible bug / crash that I can't for the life of me fix. I'm looking for either a fix to the bug, or a different approach to creating a scrollable chat control.

Here's the code I've been using. We'll start with my XAML for the chat window

<ListBox x:Name="lbChatHistory" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
    <ListBox.ItemTemplate>
    <DataTemplate>
        <Grid Background="Beige">
        <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="70"></ColumnDefinition>
        <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock x:Name="lblPlayer" Foreground="{Binding ForeColor}"  Text="{Binding Player}" Grid.Column="0"></TextBlock>
        <ContentPresenter Grid.Column="1" Width="200" Content="{Binding Message}" />
    </Grid>
    </DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

The idea is to add a new Item to the listbox. The Item (as layed out in the XAML) is a simple 2 column grid. One column for the username, and one column for the message.

Now the "items" that I add to the ListBox is a custom class. It has three properties (Player, ForeColor, and Message) that I using binding on within my XAML

Player is a string of the current user to display.

ForeColor is just a foreground color preference. It helps distinguish the difference between messages.

Message is a WrapPanel. I programmatically break the supplied string on the white space for each word. Then for each word, I add a new TextBlock element to the WrapPanel

Here is the custom class.

public class ChatMessage :DependencyObject, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public static DependencyProperty PlayerProperty = DependencyProperty.Register( "Player", typeof( string ), typeof( ChatMessage ),
                                                                                         new PropertyMetadata(
                                                                                            new PropertyChangedCallback( OnPlayerPropertyChanged ) ) );

    public static DependencyProperty MessageProperty = DependencyProperty.Register( "Message", typeof( WrapPanel ), typeof( ChatMessage ),
                                                                                         new PropertyMetadata(
                                                                                            new PropertyChangedCallback( OnMessagePropertyChanged ) ) );

    public static DependencyProperty ForeColorProperty = DependencyProperty.Register( "ForeColor", typeof( SolidColorBrush ), typeof( ChatMessage ),
                                                                                         new PropertyMetadata(
                                                                                            new PropertyChangedCallback( OnForeColorPropertyChanged ) ) );

    private static void OnForeColorPropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        ChatMessage c = d as ChatMessage;
        c.ForeColor = ( SolidColorBrush ) e.NewValue;
    }

    public ChatMessage()
    {
        Message = new WrapPanel();
        ForeColor = new SolidColorBrush( Colors.White );
    }

    private static void OnMessagePropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        ChatMessage c = d as ChatMessage;
        c.Message = ( WrapPanel ) e.NewValue;
    }

    private static void OnPlayerPropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        ChatMessage c = d as ChatMessage;
        c.Player = e.NewValue.ToString();
    }

    public SolidColorBrush ForeColor
    {
        get { return ( SolidColorBrush ) GetValue( ForeColorProperty ); }
        set
        {
            SetValue( ForeColorProperty, value );
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs( "ForeColor" ));
        }
    }

    public string Player
    {
        get { return ( string ) GetValue( PlayerProperty ); }
        set
        {
            SetValue( PlayerProperty, value );
            if ( PropertyChanged != null )
                PropertyChanged( this, new PropertyChangedEventArgs( "Player" ) );
        }
    }

    public WrapPanel Message
    {
        get { return ( WrapPanel ) GetValue( MessageProperty ); }
        set
        {
            SetValue( MessageProperty, value );
            if ( PropertyChanged != null )
                PropertyChanged( this, new PropertyChangedEventArgs( "Message" ) );
        }
    }
}

Lastly I add my items to the ListBox. Here's the simple method. It takes the above ChatMessage class as a parameter

public void AddChatItem( ChatMessage msg )
    {
        lbChatHistory.Items.Add( msg );
        lbChatHistory.ScrollIntoView( msg );
    }

Now I've tested this and it all works. The problem I'm getting is when I use the scroll bar. You can scroll down using the side scroll bar or arrow keys, but when you scroll up Silverlight crashes. FireBug returns a ManagedRuntimeError #4004 with a XamlParseException.

I'm soo close to having this control work, I can taste it! Any thoughts on what I should do or change? Is there a better approach than the one I've taken?

Thanks in advance.

UPDATE

I've found an alternative solution using a ScrollViewer and an ItemsControl instead of a ListBox control. For the most part it's stable.

A: 

I've found an alternative solution using a ScrollViewer and an ItemsControl instead of a ListBox control. For the most part it's stable.

Here's the XAML I use now instead.

<ScrollViewer x:Name="lbChatHistoryScroller">
                    <ItemsControl x:Name="lbChatHistory" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Grid Background="Beige">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="70"></ColumnDefinition>
                                        <ColumnDefinition Width="Auto"></ColumnDefinition>
                                    </Grid.ColumnDefinitions>
                                    <TextBlock x:Name="lblPlayer" Foreground="{Binding ForeColor}" Text="{Binding Player}" Grid.Column="0"></TextBlock>
                                    <ContentPresenter Grid.Column="1" Width="1750" Content="{Binding Message}">
                                    </ContentPresenter>
                                </Grid>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ScrollViewer>
Matt