Reputation: 5162
I am building a WPF app that will populate filtered headlines from a variety of news services. Each headline triggers an event, which in a console app I can display on the console. I want to use WPF here but have bot used it prior to this endeavor. My mainwindow xaml is as shown below. My original thought was to have an ObservableCollection populate list items in a listview in the xaml. If that is not the right approach, I'm open to expert opinion on a better way as speed of receipt to display is vital. If what I am doing is proper then how do I bind a new entry to the ObservableCollection to a new list item to display?
<StackPanel Orientation="Vertical" Margin="5,150 5 50" Name="HeadlinePanel">
<TextBlock Text="Filtered Headlines From Monitoring List"
HorizontalAlignment="Left" Margin="0,0 5 5" Name="ScrollingHeadlineLabel" FontWeight="Bold" FontSize="14" Background="LightSkyBlue" />
<ListBox>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline is from a website"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline is from TWTR"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline from a different website"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="text from a different tweet"/></TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox>
</StackPanel>
In the console app the streaming begins (code shown below) in the filteredStream.Start() but the handler needs to register prior. In the console app I can write to the console (commented out) but here I add the headline object to the collection when the event fires. My question is how to bind that to my xaml list items. I will initiate the stream from mainwindow method? or some method I create to run within that?
var config = new TwitterOAuthConfig()
{
ConsumerKey = customerKey,
ConsumerSecret = customerSecret,
AccessToken = accessToken,
AccessTokenSecret = accessTokenSecret,
GeoOnly = false,
KeywordsToMonitor = keywords,
UsersToFollow = followers
};
var filteredStream = new TwitterClient(config);
var headlineCollection = new ObservableCollection<Headline>();
// subscribe to the event handler
filteredStream.HeadlineReceivedEvent +=
(sender, arguments) => headlineCollection.Add(arguments.Headline);
//Console.WriteLine("ID: {0} said {1}", arguments.Headline.Username, arguments.Headline.HeadlineText);
filteredStream.ExceptionReceived += (sender, exception) => Console.WriteLine(exception.HeadlineException.ResponseMessage);
filteredStream.Start();
Here is my Original HeadlineViewModel
public class HeadlineViewModel : ObservableItem
{
private string _headlineText;
public string Source { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
public List<string> UrlsParsedFromText { get; set; }
public string TimeStamp { get; set; }
}
I've updated it to the following:
public class HeadlineViewModel
{
public class HeadlineDisplayItems: ObservableItem
{
private string _headlineText;
public string HeadlineIconPath { get; set; }
public string TimeStamp { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
}
public List<string> UrlsParsedFromText { get; set; }
public ObservableCollection<HeadlineDisplayItems> HeadlineCollection { get; set; }
}
Upvotes: 0
Views: 1633
Reputation: 106
I don't know about your architecture, but wpf is mostly used with what they call MVVM (Model-View-ViewModel) where you have your View (you already posted the code), the ViewModel (I believe you don't have one) and the model (that is the Headline you are using). The objective of the ViewModel is to simplify the life of the view and make available all the information and actions it needs to display.
For example, you should hava a ViewModel for the whole view you are building, let's say "HeadlinePanelViewModel" (I don't recommend panel in the name because the idea of using a ViewModel is to abstract the controls or technologies being used). The HeadlinePanelViewModel needs to make the headlines available, so it must have a collection of a ViewModel representing all the information concerned to the headline (icons, titles, links, ...). In the end, you have an HeadlinePanelViewModel which contains an ObservableCollection. Set this as DataContext of your View and you must be ready to go to display your info.
Now comes the part of actually loading the info. Again, I don't know about your architecture. But in VERY simple terms, you could instantiate the filteredStream inside of your HeadlinePanelViewModel and everytime an HeadlineReceivedEvent is fired, you create an HeadlineViewModel corresponding to it and add to your collection.
"Complete" code based in the code in your answer:
The ViewModel:
public class HeadlineViewModel
{
public HeadlineViewModel()
{
// This is here only for simplicity. Put elsewhere
var config = new TwitterOAuthConfig()
{
ConsumerKey = customerKey,
ConsumerSecret = customerSecret,
AccessToken = accessToken,
AccessTokenSecret = accessTokenSecret,
GeoOnly = false,
KeywordsToMonitor = keywords,
UsersToFollow = followers
};
var filteredStream = new TwitterClient(config);
HeadlineCollection = new ObservableCollection<HeadlineDisplayItems>();
// subscribe to the event handler
filteredStream.HeadlineReceivedEvent +=
(sender, arguments) => HeadlineCollection.Add(ConvertToViewModel(arguments.Headline));
//Console.WriteLine("ID: {0} said {1}", arguments.Headline.Username, arguments.Headline.HeadlineText);
filteredStream.ExceptionReceived += (sender, exception) => Console.WriteLine(exception.HeadlineException.ResponseMessage);
filteredStream.Start();
}
private HeadlineDisplayItems ConvertToViewModel(Headline headline)
{
// Conversion code here
}
public class HeadlineDisplayItems: ObservableItem
{
private string _headlineText;
public string HeadlineIconPath { get; set; }
public string TimeStamp { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
}
public List<string> UrlsParsedFromText { get; set; }
public ObservableCollection<HeadlineDisplayItems> HeadlineCollection { get; set; }
}
The View:
<StackPanel Orientation="Vertical" Margin="5,150 5 50" Name="HeadlinePanel">
<TextBlock Text="Filtered Headlines From Monitoring List"
HorizontalAlignment="Left" Margin="0,0 5 5" Name="ScrollingHeadlineLabel" FontWeight="Bold" FontSize="14" Background="LightSkyBlue" />
<ListBox ItemsSource="{Binding HeadlineCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding HeadlineIconPath}" />
<TextBlock><Run Text="{Binding Text}"/></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
The code missing is where you do the this.DataContext = new HeadlineViewModel();
to the View.
EDIT: You may experience some problems with cross-thread operations if you try to update the observableCollection from a thread different of the view thread. A workaround is to use the solution in this link, but I don't think it's the best approach.
Upvotes: 4
Reputation: 2110
Create your ObservableCollection as a Property that you can Reference in XAML. Either create it directly in your MainWindow-Class or instantiate your collection as a StaticResource.
Bind your ObservableCollection as ItemsSource to your Listbox
<ListBox ItemsSource="{Binding Path=HeadlineCollection}"></ListBox>
and use an DataTemplate to bind your data to it
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image ... />
<TextBlock Text="{Binding Path=Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
For the Headline, create a data class that manages what you need to display (headline, icons, etc.). Something like this:
class Headline
{
bool isTwitter {get; set;}
string Text {get; set;}
}
Then in your client object you can simply add a new object to the ObservableCollection by calling the Add()-Method and the Application will automatically render the new object.
You can start your query client on the main UI thread but for a responsive UI you should let the query routine run in it's own thread (e.g. by using a BackgroundWorker) so that the UI isn't cluttered by it.
Upvotes: 0