WPF updating ListBox items from method called in different class - c#

I have been banging my head against WPF ListBox item binding for hours and am hoping to get some advice. My application has three main elements:
A Player class that sends and receives data over a TcpClient.
A MainWindow that handles the GUI and exposes methods that Player can call to provide data for updating the UI based on network messages.
A UserControl called HostLobby that contains 1) a ListBox called gamesListBox for displaying new games as they are added by clients via Player and 2) UI elements for adding a new game to be broadcast to all clients.
I have confirmed that the "upstream" piece works. You can enter new game information on HostLobby, submit, and it propagates to clients as expected. In addition, clients respond properly to server messages telling them a new game has been added.
The problem is, I cannot get gameListBox to update. I rigged up test buttons on both the HostLobby control and MainWindow to verify that binding is working properly - which it is. I just can't seem to update by passing data from Player. Any ideas what I'm doing wrong?
Relevant code:
Player.cs
public void AddGameToLobby(string name, int mp)
{
// name and mp are provided by the network message handler and work fine
WriteToLog("Attempting to add game to host lobby", true);
mainWindow.AddGameToLobby(name, mp);
}
MainWindow.cs
public void AddGameToLobby(string n, int mp)
{
hostLobby.AddGameToList(n, mp);
}
HostLobby.cs
private MainWindow parent; // used to call an AddGame event when client adds a game
public ObservableCollection<Game> games;
public class Game
{
public string Name { get; set; }
}
public HostLobby(MainWindow mw)
{
InitializeComponent();
parent = mw;
games = new ObservableCollection<Game>();
// add some test items - this works. the ListBox updates properly
games.Add(new Game() { Name = "game1" });
games.Add(new Game() { Name = "game2" });
games.Add(new Game() { Name = "game3" });
gamesListBox.ItemsSource = games;
}
public void AddGameToList(string n, int maxp)
{
// called to announce a new game added by another client
// call stack is Player.AddGameToLobby -> MainWindow.AddGameToLobby -> this.AddGameToList
string msg = String.Format("{0} (0/{1})", n, maxp.ToString());
games.Add(new Game() { Name = msg });
}
HostLobby.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="50" Orientation="Vertical" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Label HorizontalContentAlignment="Right" Width="80">Game Name:</Label>
<TextBox Name="gameNameTextBox" VerticalContentAlignment="Center" Width="200"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Label HorizontalContentAlignment="Right" Width="80">Max Players:</Label>
<TextBox Name="maxPlayersTextBox" VerticalContentAlignment="Center" Width="200"/>
</StackPanel>
<Button Name="addGameButton" Click="addGameButton_Click" Margin="0,20,0,0" Width="200" Height="30">Add Game</Button>
</StackPanel>
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="50" Grid.Column="1">
<Label>Current Games</Label>
<ListBox Name="gamesListBox" MinHeight="200">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>

Your ListBox is not being updated because you're modifying the ObservableCollection on a worker thread, which means that the collection's CollectionChanged event is being raised on the worker thread as well. To remedy this, you need to modify your list on the UI thread. To achieve this, you have a few options, but the first ones that come to mind are:
Using Dispatcher.BeginInvoke
In AddGameToList in HostLobby.cs, put the statement that modifies the list inside a Dispatcher.BeginInvoke:
Application.Current.Dispatcher.BeginInvoke((MethodInvoker)(() => games.Add(new Game() { Name = msg })));
Using BindingOperations.EnableCollectionSynchronization (.NET 4.5 or later)
First, create a lock object as a private member of your HostLobby class. Then, after initializing your ObservableCollection, call the following:
BindingOperations.EnableCollectionSynchronization(games, _yourLockObj);
Then, use the lock whenever you modify the list within HostLobby. So in this instance, you'd want to change the list modification in AddGameToList such that it uses the lock:
lock (_yourLockObj)
{
games.Add(new Game() { Name = msg });
}
The latter seems like a better choice in my opinion, but it is only available if you're using .NET 4.5 or later.

Related

Accessing TextBox Text Inside GridView (C# / XAML)

So I have a customised GridView with a data template that contains a TextBox and is populated by a list of a custom class called Player. I need to be able to retrieve both the instance of Player and the text in the TextBox and save them to a new custom class called Score.
<GridView x:Name="gridScore" ItemsSource="{x:Bind PlayerList}" IsItemClickEnabled="True">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:Player">
<StackPanel Orientation="Horizontal">
<TextBox x:Name="txtbxGridScore" TextChanged="txtbxGridScoreChangedEventHandler" />
<Image Source="{x:Bind ProfilePicture}"/>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{x:Bind FullName}" />
<TextBlock Text="{x:Bind Alias}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<Button x:Name="buttonSave" Content="Save Scores" Style="{StaticResource BarButtonStyle}" Click="buttonSave_Click"/>
I come from a web-based Java background so this is a little bit new to me but it seems like it should be a fairly simple exercise.
Initially, I tried iterating through the GridView upon a Button Click and grabbing each Player item along with the TextBox Text and saving them to a List<> of Score, however, getting the TextBox value proved troublesome.
I then tried initialising a page scope List<> of Score and simply updating it each time the TextBox value was changed, however, I wasn't able to make this work either.
A solution for either approach will work fine for my purposes. Any input is appreciated!
If I correctly understood you this is one of the way to resolve your problem.
So let's assume that your model class Player have this structure:
public class Player {
public int PlayerID { get; set; }
public string ProfilePicture { get; set; }
public string FullName { get; set; }
public string Alias { get; set; }
public float PlayerScore { get; set; } // To store textbox value
}
So you can resolve this by using two way binding.
XAML part will look something like this:
<GridView x:Name="gridScore"
ItemsSource="{x:Bind PlayerList}"
IsItemClickEnabled="True">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:Player">
<StackPanel Orientation="Horizontal">
<TextBox x:Name="txtbxGridScore"
Text="{x:Bind PlayerScore, Mode=TwoWay}" />
<Image Source="{x:Bind ProfilePicture}" />
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{x:Bind FullName}" />
<TextBlock Text="{x:Bind Alias}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<Button x:Name="buttonSave"
Content="Save Scores"
Click="buttonSave_Click" />
I have initialized your PlayerList with some dummy data like this:
PlayerList = new ObservableCollection<Player>() {
new Player() {
FullName = "Player A", Alias = "AAA"
},
new Player () {
FullName = "Player B", Alias = "BBB"
}
};
As you can see in XAML I am binding your text box with PlayerScore property of Player model.
When I run this App I get screen like this:
I will input some data into TextBox and click Save button:
When I click on Save it will trigger the event that you wrote in Button part
In that event I have one foreach loop that will iterate through the list and one breakpoint and as you can see on first item "Player A" the PlayerScore value is 10:
Now you can find your players with some ID property or with some other way that you want. This is the most simple way to accomplish what you want.
Remark: This could be solved in a better way using MVVM pattern and other stuff but as you mentioned you are beginner so maybe it is better for you to solve it like this and after that go with more advanced technique. Hope that this was helpful for you.

UWP ObservableCollection with data from webservice not updating GUI

I'm using an ObservableCollection on a listview. The ObservableCollection get its data correctly from a web service using an async method then parses it and fill the ObservableCollection with the Add method on the NewsProxyParser(string thing, out ObservableCollection newsList) method, but i'm not able to update the GUI with the binded ObservableCollection.
I'm new to UWP, so i suppose that the async method runs on a different thread that the GUI. How can i do correctly the bind to update the GUI with the data fetched from the web service?
My XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView Name="ListViewNews" ItemsSource="{x:Bind NewsCollection, Mode=OneWay}" IsItemClickEnabled="True" ItemClick="ListView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:News">
<RelativePanel Margin="0,16" >
<Image Name="x:Thumb" Source="{x:Bind thumbnailUrl}" Width="96" Height="96" RelativePanel.AlignLeftWithPanel="True" Margin="0,0,16,0"></Image>
<TextBlock Name="x:Title" Text="{x:Bind title}" FontSize="22" FontWeight="Bold" RelativePanel.RightOf="x:Thumb"></TextBlock>
<TextBlock Name="x:Date" Text="{x:Bind dateString}" FontSize="12" FontWeight="Light" Foreground="Gray" RelativePanel.RightOf="x:Thumb" RelativePanel.Below="x:Title"></TextBlock>
<TextBlock Name="x:Text" Text="{x:Bind newsText}" Foreground="Gray" RelativePanel.RightOf="x:Thumb" RelativePanel.Below="x:Date"></TextBlock>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
And the cs:
public sealed partial class NewsPage : Page
{
private ObservableCollection<News> NewsCollection;
public NewsPage()
{
NewsRequest();
this.InitializeComponent();
}
private async void NewsRequest()
{
//Fetch News from web
string response = await NewsProxy.GetNews("TokenX");
//Parse News to add to NewsCollection
bool success = NewsProxy.NewsProxyParser(response, out NewsCollection);
// success is true and NewsCollection has new updated values
if (success)
{
News n = new News();
n.title = "Just to trigger collectionchanged";
n.newsText = "dadada";
n.order = 1;
NewsCollection.Add(n);
Debug.WriteLine("Still No GUI updated!!!");
}
}
}
ObservableCollection<> only notifies of collection changes, such as adding and removing items. As long as you replace the whole collection, you will need to make NewsCollection a dependency property to notify XAML UI about the changes.
public ObservableCollection<News> NewsCollection
{
get { return (ObservableCollection<News>)GetValue(NewsCollectionProperty); }
set { SetValue(NewsCollectionProperty, value); }
}
public static readonly DependencyProperty NewsCollectionProperty =
DependencyProperty.Register("NewsCollection", typeof(ObservableCollection<News>), typeof(NewsPage), new PropertyMetadata(null));
private async void NewsRequest()
{
//Fetch News from web
string response = await NewsProxy.GetNews("TokenX");
//Parse News to add to NewsCollection
ObservableCollection<News> newsCollection;
bool success = NewsProxy.NewsProxyParser(response, out newsCollection);
NewsCollection = newsCollection;
// success is true and NewsCollection has new updated values
if (success)
{
News n = new News();
n.title = "Just to trigger collectionchanged";
n.newsText = "dadada";
n.order = 1;
NewsCollection.Add(n);
Debug.WriteLine("Still No GUI updated!!!");
}
}

Windows phone 7 listbox corrupted data

I'm trying to display information through a webservice in a ListBox which has TextBlock bindings. I've been researching this issue online and can't seem to resolve this problem. My database tables do hold proper data. Lastly, I've got a page that provides the same exact functions/methods and it displays proper data. I'm really troubled by this issue and was hoping one of you out there could help out!
I'm unable to upload a screenshot of my ListBox but I'll try my best to illustrate it here!
Purchase Order(Title)
Purchase Order No: 1 (ListItem)
Status: Accepted (ListItem)
-----Corrupted data appears after the first row has been shown-----
Purchase Order No: 0
Purchase Order No: 0
----End----
Note that my first row displays correct data whereas the others are all corrupted.
However, when implementing a search function, i'm able to view existing data from the database properly.(Not corrupted).
PurchaseOrder.aspx:
<controls:PivotItem Header="Purchase Order" FontSize="18">
<ScrollViewer VerticalScrollBarVisibility="Visible">
<ListBox x:Name="ListBoxPurchaseOrder" Margin="20,42,38,0" d:LayoutOverrides="VerticalAlignment">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding purchaseOrderID, StringFormat='Purchase Order No: \{0\}'}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" FontSize="38" />
<TextBlock Text="{Binding status, StringFormat='Status: \{0\}'}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</controls:PivotItem>
PurchaseOrder.aspx.cs:
public PO()
{
load();
InitializeComponent();
}
bool success = false;
Session.PurchaseOrder session = new Session.PurchaseOrder();
private void load()
{
InitializeComponent();
IfsRetailerClient myClient = new IfsRetailerClient();
myClient.getPurchaseOrder1Completed += new EventHandler<getPurchaseOrder1CompletedEventArgs>(myClient_getPurchaseOrder1Completed);
myClient.getPurchaseOrder1Async();
}
void myClient_getPurchaseOrder1Completed(object sender, getPurchaseOrder1CompletedEventArgs e)
{
ListBoxPurchaseOrder.ItemsSource = e.Result;
}
Webservice file, IfsRetailer.cs:
[OperationContract]
List<Payment> getPayment();
fsRetailer.cs:
DatabaseEntities db = new DatabaseEntities();
public List<Payment> getPayment()
{
return db.Payments.ToList();
}

Parallel Generation of UI

We have a WPF application that has a ListBox with a VirtualizingStackPanel with caching. Not because it has massively many elements (typically less than 20 but perhaps up to 100 or more in extreme cases) but because elements take time to generate. The elements are in fact UIElement objects. So the application dynamically needs to generate UIElements.
The problem is that even though the virtualization appears to work, the application is still slow to become responsive, and this is in a proof of concept solution with minimal "noise".
So we figured that since the main problem is that we generate complex UIElement objects dynamically, we need to do that in parallel, i.e. off-thread. But we get an error that the code needs to be run on a STA thread:
The calling thread must be STA, because many UI components require this.
Does this mean that we cannot generate UI (UIElement objects) on thread other than the WPF main UI thread?
Here's a relevant code fragment from our proof of concept solution:
public class Person : ObservableBase
{
// ...
UIElement _UI;
public UIElement UI
{
get
{
if (_UI == null)
{
ParallelGenerateUI();
}
return _UI;
}
}
private void ParallelGenerateUI()
{
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => GenerateUI())
.ContinueWith(t =>
{
_UI = t.Result;
RaisePropertyChanged("UI");
}, scheduler);
}
private UIElement GenerateUI()
{
var tb = new TextBlock();
tb.Width = 800.0;
tb.TextWrapping = TextWrapping.Wrap;
var n = rnd.Next(10, 5000);
for (int i = 0; i < n; i++)
{
tb.Inlines.Add(new Run("A line of text. "));
}
return tb;
}
// ...
}
and here is a relevant piece of XAML:
<DataTemplate x:Key="PersonDataTemplate" DataType="{x:Type local:Person}">
<Grid>
<Border Margin="4" BorderBrush="Black" BorderThickness="1" MinHeight="40" CornerRadius="3" Padding="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<!--<RowDefinition />-->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Name : " Grid.Row="0" FontWeight="Bold" HorizontalAlignment="Right" />
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Name}" />
<TextBlock Text=" - Age : " Grid.Column="2" Grid.Row="0" FontWeight="Bold"
HorizontalAlignment="Right" />
<TextBlock Grid.Column="3" Grid.Row="0" Text="{Binding Age}" />
<ContentControl Grid.Column="4" Grid.Row="0" Content="{Binding Path=UI}" />
</Grid>
</Border>
</Grid>
</DataTemplate>
As you can see we databind to a property UI of type UIElement.
<ListBox x:Name="listbox" ItemsSource="{Binding Persons}" Background="LightBlue"
ItemTemplate="{StaticResource PersonDataTemplate}"
ItemContainerStyle="{StaticResource ListBoxItemStyle}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingStackPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.CacheLength="10,10"
VirtualizingStackPanel.CacheLengthUnit="Item"
>
<ListBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ListBox.GroupStyle>
</ListBox>
In closing context, what our application does is create a code view where the list is of procedures which again contain a mix of structured content (for parameters and local variables on one hand and statements and expressions on the other.)
In other words our UIElement objects are too complex to create via databinding alone.
Another thought we had was to use "Async" settings in the XAML as it appears possible to create "non-blocking UI" but we have not been able to implement this because we get the same error as above:
The calling thread must be STA, because many UI components require this.
Stacktrace:
System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=The calling thread must be STA, because many UI components require this.
Source=PresentationCore
StackTrace:
at System.Windows.Input.InputManager..ctor()
at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
at System.Windows.Input.KeyboardNavigation..ctor()
at System.Windows.FrameworkElement.FrameworkServices..ctor()
at System.Windows.FrameworkElement.EnsureFrameworkServices()
at System.Windows.FrameworkElement..ctor()
at System.Windows.Controls.TextBlock..ctor()
at WPF4._5_VirtualizingStackPanelNewFeatures.Person.GenerateUI() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 84
at WPF4._5_VirtualizingStackPanelNewFeatures.Person.<ParallelGenerateUI>b__2() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 68
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
InnerException:
Edits:
1) Added more XAML.
2) Added stacktrace.
I am suffering the same problem in normal c# environment. I also tried lots of things. Do you calculate the size of controls to adjust the size of the parent in advance? I am doing this unfortunately.
You may also create a control nesting your children dynamically. By that you can create kind of an UIElement Adapter. The adapter is created at the start time and has all information to create the UIElements.
The adapter could create requested children on STA thread on demand just in time. When scrolling up or down you may create children in advance in the direction you are scrolling. This way you can start with e.g. 5-10 UI elements and then you calculate by scrolling up more.
I know this is not so nice and it would be better, if there is some technology within the framework providing something like this, but I did not found it yet.
You may look also at those two things. One helped me much in control responsive. The other is still open, since you need .NET Framework 4.5:
SuspendLayout and ResumeLayout don't operate very nice. You may try this:
/// <summary>
/// An application sends the WM_SETREDRAW message to a window to allow changes in that
/// window to be redrawn or to prevent changes in that window from being redrawn.
/// </summary>
private const int WM_SETREDRAW = 11;
/// <summary>
/// Suspends painting for the target control. Do NOT forget to call EndControlUpdate!!!
/// </summary>
/// <param name="control">visual control</param>
public static void BeginControlUpdate(Control control)
{
Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgSuspendUpdate);
}
/// <summary>
/// Resumes painting for the target control. Intended to be called following a call to BeginControlUpdate()
/// </summary>
/// <param name="control">visual control</param>
public static void EndControlUpdate(Control control)
{
// Create a C "true" boolean as an IntPtr
IntPtr wparam = new IntPtr(1);
Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgResumeUpdate);
control.Invalidate();
control.Refresh();
}
Dispatcher.Yield
You can't change items on the UI thread from a different thread. It should work if you have a delegate on the UI thread which handles actually adding the item to the UI.
Edit:
From here:
It appears there are deeper issues with using the SynchronizationContext for UI threading.
SynchronizationContext is tied in with the COM+ support and is
designed to cross threads. In WPF you cannot have a Dispatcher that
spans multiple threads, so one SynchronizationContext cannot really
cross threads.
If it is just a one row template then consider ListView GridView.
As for dynamic content rather then dynamic UI elements use a single UI element that displays formatted content (runs, hyperlink, table).
Consider FlowDocument for Dynamic content.
FlowDocument Class
The FlowDocument can be created in background.
Also see priority binding.
PriorityBinding Class
Then you can display it with FlowDocumentScrollViewer or three other options.
I suspect adding UI elements dynamically breaks virtualization as it cannot reuse UI elements..
Have you tried:
ItemsSource="{Binding Persons, IsAsync=True}"
Or if you wand to go async in code behind, Dispatcher can help
private void ParallelGenerateUI()
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)delegate()
{
_UI = GenerateUI();
RaisePropertyChanged("UI");
});
}
Just tested you code below and I get no errors:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 10000; i++)
{
Persons.Add(new Person());
}
}
private ObservableCollection<Person> myVar = new ObservableCollection<Person>();
public ObservableCollection<Person> Persons
{
get { return myVar; }
set { myVar= value; }
}
}
public class Person : INotifyPropertyChanged
{
// ...
UIElement _UI;
public UIElement UI
{
get
{
if (_UI == null)
{
ParallelGenerateUI();
}
return _UI;
}
}
private void ParallelGenerateUI()
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)delegate()
{
_UI = GenerateUI();
NotifyPropertyChanged("UI");
});
}
private UIElement GenerateUI()
{
Random rnd = new Random();
var tb = new TextBlock();
tb.Width = 800.0;
tb.TextWrapping = TextWrapping.Wrap;
var n = rnd.Next(10, 5000);
for (int i = 0; i < n; i++)
{
tb.Inlines.Add(new Run("A line of text. "));
}
return tb;
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
However I do not know what ObservableBase is doing

WPF: Update Listbox automatically C#

I have two WPF windows developed using the surface SDK, one that is a data entry form, and the second dispays the data in a listbox. The listbox displays the data perfectly but when I add a new record using the data entry form, the listbox is not updated until I reopen the window. Is there a way to automatically update the listbox through binding or something?
This is the listbox code:
<s:SurfaceListBox Height="673" Margin="0,26,0,31" Name="surfaceListBox1" ItemsSource="{Binding Path={}}" Width="490">
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Width="80" FontSize="8" Content="{Binding Path=item1}"></Label>
<Label Width="80" FontSize="8" Content="{Binding Path=item2}"></Label>
<Label Width="210" FontSize="8" Content="{Binding Path=item3}"></Label>
<Label Width="80" FontSize="8" Content="{Binding Path=item4}"></Label>
<Label Width="60" FontSize="8" Content="{Binding Path=item5, Converter={StaticResource booleanconverter}}"></Label>
</StackPanel>
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
</s:SurfaceListBox>
I am using Visual C# 2008 and the code to fill the listbox is:
private SHIPS_LOGDataSet ShipData = new SHIPS_LOGDataSet();
private SHIPS_LOGDataSetTableAdapters.MAINTableAdapter taMain = new SHIPS_LOGDataSetTableAdapters.MAINTableAdapter();
private SHIPS_LOGDataSetTableAdapters.TableAdapterManager taManager = new ShipsLogSurface.SHIPS_LOGDataSetTableAdapters.TableAdapterManager();
private void SurfaceWindow_Loaded(object sender, RoutedEventArgs e)
{
this.taMain.Fill(this.ShipData.MAIN);
this.DataContext = from MAIN in this.ShipData.MAIN orderby MAIN.MESSAGE_ID descending select MAIN;
}
The only table in my database is called MAIN.
I'm guessing I might have to use a collection view or similar but don't know how to implement that. Any ideas would be much appreciated. Thanks
INotifyPropertyChanged is an interface which you should implement in your data class (ShipData?). The properties in your data class should look as follows:
private string _myField;
public string MyField {
get { return _myField; }
set { _myField = value; onPropertyChanged(this, "MyField"); }
}
So whenever something in your data class changes (i.e. add/delete/update), it will fire the OnPropertyChanged event.
Your List or ObservableCollection that you use to populate the list listens to this OnPropertyChanged event and will update itself whenever the event is fired.
Try to do it with INotifyPropertyChanged.
surfaceListBox1.Items.Refresh();

Categories