WP7 - Adicionar à coleção em Dispatcher

Estou baixando alguns xml de um feed do Twitter em segundo plano, usando HttpWebRequest e Deployment.Current.Dispatcher.BeginInvoke() para processar os resultados e defini-los como ItemsSource do meu ListBox . O que eu quero fazer agora é reunir o xml de vários feeds do Twitter, combiná-los em uma coleção e atribuir isso à propriedade ItemsSource .

Eu imaginei que usaria um contador na classe e uma coleção na classe e atualizasse cada vez que uma solicitação fosse concluída e, quando o contador atingisse a contagem de feeds (7), definisse o ItemsSource adequadamente. O problema é que sou muito novo em C #/WP7 e estou tendo alguns problemas aqui. Isto é o que eu tenho trabalhando agora, o que é obviamente errado, porque qualquer requisição termina por último sobrescreve o ItemsSoure , E, eu não sei como colocá-los em um container 'global', porque parece como o Dispatcher tem um escopo diferente:

string[] feeds = { "badreligion",
                "DoctorGraffin",
                "BrettGurewitz",
                "jay_bentley",
                "brtour",
                "GregHetson",
                "theBRpage" };

// invoked in the constructor
private void StartTwitterUpdate()
{
    foreach (string feed in feeds)
    {
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=" + feed));

        request.BeginGetResponse(new AsyncCallback(twitter_DownloadStringCompleted), request);
    }
}

// the AsyncCallback
void twitter_DownloadStringCompleted(IAsyncResult asynchronousResult)
{
    HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;

    HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);

    using (StreamReader streamReader1 =
        new StreamReader(response.GetResponseStream()))
    {
        string resultString = streamReader1.ReadToEnd();

        XElement xmlTweets = XElement.Parse(resultString);

       //here I need to add to a collection, and if the max is hit, set the ItemsSource
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            TwitterListBox.ItemsSource = from tweet in xmlTweets.Descendants("status")
                                            select new TwitterItem
                                            {
                                                CreatedAt = tweet.Element("created_at").Value,
                                                Id = tweet.Element("id").Value,
                                                ImageSource = tweet.Element("user").Element("profile_image_url").Value,
                                                Message = tweet.Element("text").Value,
                                                UserName = "@" + tweet.Element("user").Element("screen_name").Value
                                            };
        });
    }
}

EDIT Also, if it matters, I'll have to sort the final collection before sending it to ItemsSource, via TwitterItem.CreatedAt, so if someone could suggest an optimal data structure for sorting and easy ItemsSource assignment, that'd be great!

1
Eu acho que qualquer estrutura de dados simples como List ou ObservableCollection é atualizável "em Dispatchers" ou de um thread em segundo plano, o problema é que se for ligado a um elemento da interface do usuário - o elemento da interface pode não gostar de ser acessado um thread em segundo plano, portanto, você deve atualizar apenas as coleções vinculadas de threads em segundo plano usando Dispatcher.Invoke (() => updatecall);
adicionado o autor Filip Skakun, fonte

2 Respostas

O que você precisa fazer é criar um objeto de coleção persistente e vinculável em algum ponto da sua visualização e atribuí-lo ao ItemsSource uma vez. À medida que novos itens são recebidos, basta adicioná-los à coleção.

Você desejará escolher um tipo de coleção existente que suporte notificações quando a coleção for modificada ou implementar uma delas por conta própria. No Silverlight, acho que a sua melhor (única?) Aposta é o System.Collections.ObjectModel.ObservableCollection . A outra opção é implementar System.Collections.Specialized.INotifyCollectionChanged em uma classe personalizada que herda de um tipo diferente de Collection.

Quanto à classificação, se os dados que chegam sempre serão posteriores aos dados existentes, você pode simplesmente classificar os novos itens antes de adicioná-los à coleção (você pode querer inseri-los na frente da coleção se quiser exibir eles com o mais novo no topo).

No entanto, se toda a coleção precisar ser classificada sempre que novos registros forem adicionados, será necessário implementar System.IComparable na sua classe de item. Nesse caso, eu recomendaria a seguinte abordagem:

Crie uma nova classe de coleção baseada em System.Collection.Generic.List (isso contém um método nativo Sort ()).

Implemente INotifyCollectionChanged nesta classe e aumente seu evento CollectionChanged com a ação NotifyCollectionChangedAction.Reset depois que seus registros forem adicionados e classificados.

Implemente IComparable na sua classe de item para obter os itens classificados de acordo com suas regras.

Atualizar com a implementação do INotifyCollectionChanged

A maior parte da sugestão é bem direta, mas a implementação do INotifyCollectionChanged é um pouco complicada, então estou incluindo aqui:

 _
Private m_ListChangedEvent As NotifyCollectionChangedEventHandler

''' 
''' This event is raised whenever the list is changed and implements the IBindingList ListChanged event '''
 
''' 
''' 
''' 
Public Custom Event ListChanged As NotifyCollectionChangedEventHandler Implements INotifyCollectionChanged.CollectionChanged
     _
    AddHandler(ByVal value As NotifyCollectionChangedEventHandler)
        m_ListChangedEvent = DirectCast([Delegate].Combine(m_ListChangedEvent, value), NotifyCollectionChangedEventHandler)
    End AddHandler

     _
    RemoveHandler(ByVal value As NotifyCollectionChangedEventHandler)
        m_ListChangedEvent = DirectCast([Delegate].Remove(m_ListChangedEvent, value), NotifyCollectionChangedEventHandler)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
        If m_ListChangedEvent IsNot Nothing Then
            m_ListChangedEvent.Invoke(sender, e)
        End If
    End RaiseEvent
End Event

Para aumentar este evento para que os consumidores estejam cientes das alterações na lista:

Call RaiseListChangedEvent(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
2
adicionado
Soa difícil (mas divertido)! Eu estou supondo que olhar através do MSDN vai me dar o que eu preciso saber para fazer uma coisa dessas? Se não, você tem algum bom tutorial/links para implementações gerais de notificação e assim por diante?
adicionado o autor Josh, fonte
Não tem problema, eu realmente aprecio tudo o que você me deu. Eu sou um desenvolvedor profissional, mas só tenho feito MS/C # codificando por cerca de 5 dias agora !!!
adicionado o autor Josh, fonte
A implementação de INotifyCollectionChanged é a mais desafiadora, mas incluí minha solução na resposta. O resto deve ser facilmente detectável (desculpe, eu não tenho nenhum link específico desde que implementamos este ano passado).
adicionado o autor competent_tech, fonte
Desculpe, esqueci que isso era uma pergunta C #; o código que eu adicionei era VB, mas deveria haver traduções lá fora. A nota importante é que, se a classe do item for serializada, você não desejará serializar o evento com ela, já que é tecnicamente um membro serializável, que eu nunca entendi de fato.
adicionado o autor competent_tech, fonte

Uma solução simples seria usar um ObservableCollection como seu ItemsSource:

TwitterListBox.ItemsSource = new ObservableCollection().

Sempre que você receber mais itens para adicionar - simplesmente faça:

var itemsSource = (ObservableCollection)TwitterListBox.ItemsSource;

foreach(var twitterItem in newTweets)
{
    itemsSource.Add(twitterItem);
}

Se você quiser que estes classificados - você precisa fazer itemsSource.Insert (twitterItem, i) depois de descobrir onde inserir o novo item. Provavelmente, existem algumas maneiras de fazer isso, mas supondo que você analise seu created_at assim:

CreatedAt = DateTime.ParseExact(createdAt, "ddd MMM dd HH:mm:ss zzz yyyy", CultureInfo.InvariantCulture)

Isso é mais ou menos como você poderia fazer isso:

int i = 0;//insert index
int j = 0;//new items index

while (j < newTweets.Count)
{
    while (i < itemsSource.Count &&
        itemsSource[i].CreatedAt >= newTweets[j].CreatedAt)
    {
        i++;
    }

    itemsSource.Insert(i, newTweets[j]);
    j++;
}

Ou uma solução mais chique:

public partial class MainPage : PhoneApplicationPage
{
   //Constructor
    public MainPage()
    {
        InitializeComponent();

        var itemsSource = new ObservableCollection();
        var initialTweets = new[]
                                {
                                    new TwitterItem
                                        {CreatedAt = DateTime.Now.AddMinutes(-3)},
                                    new TwitterItem
                                        {CreatedAt = DateTime.Now.AddMinutes(-2)},
                                    new TwitterItem
                                        {CreatedAt = DateTime.Now.AddMinutes(-1)}
                                };
        itemsSource.Merge(initialTweets.OrderByDescending(ti => ti.CreatedAt));

        var newTweets = new List();
        newTweets.Add(new TwitterItem {CreatedAt = DateTime.Now.AddMinutes(-3.5)});
        newTweets.Add(new TwitterItem {CreatedAt = DateTime.Now.AddMinutes(-2.5)});
        newTweets.Add(new TwitterItem {CreatedAt = DateTime.Now.AddMinutes(-1.5)});
        newTweets.Add(new TwitterItem {CreatedAt = DateTime.Now.AddMinutes(-0.5)});
        itemsSource.Merge(newTweets.OrderByDescending(ti => ti.CreatedAt));

        foreach (var twitterItem in itemsSource)
        {
            Debug.WriteLine(twitterItem.CreatedAt.ToString());
        }
    }
}

public class TwitterItem
{
    public DateTime CreatedAt;
}

public static class ObservableTwitterItemsExtensions
{
    public static void Merge(
        this ObservableCollection target, IEnumerable source)
    {
        int i = 0;//insert index

        foreach (var newTwitterItem in source)
        {
            while (i < target.Count &&
                target[i].CreatedAt >= newTwitterItem.CreatedAt)
            {
                i++;
            }

            target.Insert(i, newTwitterItem);
        }
    }
}
1
adicionado
Técnicos em Informática
Técnicos em Informática
2 517 dos participantes

Um grupo com foco em assuntos técnicos, hardware, servidores e resolução de problemas em Windows e Linux. Canal: @dicasdeti