ObservableCollection <t>不更新UI </t>

时间:2008-12-08 16:00:06

标签: wpf wcf multithreading collections

我遇到了ObservableCollection获取新项目但没有在ListView中反映这些更改的问题。我在实施这个问题的方式上有足够的怪癖,我很难确定问题所在。

我的ObservableCollection如此实施:

public class MessageList : ObservableCollection<LobbyMessage>
{
    public MessageList(): base()
    {
        Add(new LobbyMessage() { Name = "System", Message = "Welcome!" });
    }
}

我将集合存储在静态属性中(以便可以从多个用户控件轻松访问):

static public MessageList LobbyMessages { get; set; }

在我的主要NavigationWindow的OnLoad事件中,我有以下一行:

ChatHelper.LobbyMessages = new MessageList();

ListView所在的UserControl中的我的XAML读作:

    <ListBox IsSynchronizedWithCurrentItem="True" 
         ItemsSource="{Binding Mode=OneWay}" 
         x:Name="ListBoxChatMessages" 
         d:UseSampleData="True" 
         ItemTemplate="{DynamicResource MessageListTemplate}" 
         IsEnabled="True">
      <ListBox.DataContext>
        <Magrathea_Words_Tools:MessageList/>
       </ListBox.DataContext>
    </ListBox>

我在构造函数中添加的初始消息在UI中显示得很好。

现在,我向集合添加新项目的方式来自来自WCF服务的CallBack。我有这个代码在WinForms应用程序中工作,并且必须将回调编组到UI线程,所以我保留了代码。以下是该方法的缩写版本:

Helper.Context = SynchronizationContext.Current;

#region IServiceMessageCallback Members

/// <summary>
/// Callback handler for when the service has a message for 
/// this client
/// </summary>
/// <param name="serviceMessage"></param>
public void OnReceivedServiceMessage(ServiceMessage serviceMessage)
{
    // This is being called from the WCF service on it's own thread so
    //  we have to marshall the call back to this thread.
    SendOrPostCallback callback = delegate
    {
        switch (serviceMessage.MessageType)
        {
            case MessageType.ChatMessage:
                ChatHelper.LobbyMessages.Add(
                        new LobbyMessage()
                        {
                            Name = serviceMessage.OriginatingPlayer.Name,
                            Message = serviceMessage.Message
                        });
                break;

            default:
                break;
        }
    };

    Helper.Context.Post(callback, null);
}

在调试时,我可以看到集合使用来自服务的消息进行更新,但UI并未反映这些添加内容。

有关我缺少什么的想法,以使ListView反映集合中的这些新项目?

2 个答案:

答案 0 :(得分:4)

我解决了这个问题。

静态属性或传入数据的上下文都与问题无关(事后看来这很明显)。

由Expression Blend生成的XAML由于某种原因无法完成任务。我所做的就是将ItemSource分配给C#中的集合。

ListBoxChatMessages.ItemsSource = ChatHelper.LobbyMessages.Messages;

我的XAML现在更加简化了。

<ListBox IsSynchronizedWithCurrentItem="True" 
       ItemsSource="{Binding Mode=OneWay}" Background="#FF1F1F1F" 
       Margin="223,18.084,15.957,67.787" x:Name="ListBoxChatMessages" 
       ItemTemplate="{DynamicResource MessageListTemplate}" 
       IsEnabled="True"/>

我对这为何如此有效感到困惑。我正在阅读有关如何在WPF中绑定数据的MSDN文章,它们包括几个绑定对象,引用对象的属性等。我不明白为什么当UserControl的构造函数中的一行代码执行时,他们遇到了所有麻烦诀窍很好。

答案 1 :(得分:0)

你需要在ObservableCollection中实现你的poco类INotifyPropertyChanged。

示例:

<viewModels:LocationsViewModel x:Key="viewModel" />
.
.
.    
<ListView
    DataContext="{StaticResource viewModel}"
    ItemsSource="{Binding Locations}"
    IsItemClickEnabled="True"
    ItemClick="GroupSection_ItemClick"
    ContinuumNavigationTransitionInfo.ExitElementContainer="True">

    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}" Margin="0,0,10,0" Style="{ThemeResource ListViewItemTextBlockStyle}" />
                <TextBlock Text="{Binding Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="0,0,5,0"/>
                <TextBlock Text="{Binding Longitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="5,0,0,0" />
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

public class LocationViewModel : BaseViewModel
{
    ObservableCollection<Location> _locations = new ObservableCollection<Location>();
    public ObservableCollection<Location> Locations
    {
        get
        {
            return _locations;
        }
        set
        {
            if (_locations != value)
            {
                _locations = value;
                OnNotifyPropertyChanged();
            }
        }
    }
}

public class Location : BaseViewModel
{
    int _locationId = 0;
    public int LocationId
    {
        get
        {
            return _locationId;
        }
        set
        {
            if (_locationId != value)
            {
                _locationId = value;
                OnNotifyPropertyChanged();
            }
        }
    }

    string _name = null;
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            if (_name != value)
            {
                _name = value;
                OnNotifyPropertyChanged();
            }
        }
    }

    float _latitude = 0;
    public float Latitude 
    { 
        get
        {
            return _latitude;
        }
        set
        {
            if (_latitude != value)
            {
                _latitude = value;
                OnNotifyPropertyChanged();
            }
        }
    }

    float _longitude = 0;
    public float Longitude
    {
        get
        {
            return _longitude;
        }
        set
        {
            if (_longitude != value)
            {
                _longitude = value;
                OnNotifyPropertyChanged();
            }
        }
    }
}

public class BaseViewModel : INotifyPropertyChanged
{
    #region Events
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    protected void OnNotifyPropertyChanged([CallerMemberName] string memberName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(memberName));
        }
    }
}