Why isnt my ObservableCollection updating my UI? - c#

I'm pretty sure I've missed something super tiny but I cant for the life of me figure out where I went wrong.
I'm trying to bind my datagrid to the ConsoleLines property which is infact getting input to it, I debugged it and the ConsoleLines contains multiple strings.
But for some reason its not updating the UI and adding the lines to the gridview.. Or well technically the TextBlock.
So I have my MainWindows setup like this
public partial class MainWindow : Window
{
Server Server = new Server();
public MainWindow()
{
InitializeComponent();
DataContext = new MasterViewModel();
}
private void BtnStart_OnClick(object sender, RoutedEventArgs e)
{
Server.StartServer();
}
}
As you can see I am setting the DataContext to a new instance of the MasterViewModel which looks like this
public class MasterViewModel
{
public Server Server { get; }
= new Server();
}
And here is my Server class
public class Server : INotifyPropertyChanged
{
public Process pServer;
public Server()
{
}
public ObservableCollection<string> ConsoleLines { get; }
= new ObservableCollection<string>();
public void StartServer()
{
pServer = new Process();
pServer.StartInfo.FileName = "java";
pServer.StartInfo.Arguments = #"-jar " + "-Xms512M -Xmx1G spigot.jar";
pServer.StartInfo.UseShellExecute = false;
pServer.StartInfo.RedirectStandardOutput = true;
pServer.StartInfo.RedirectStandardError = true;
pServer.StartInfo.RedirectStandardInput = true;
pServer.OutputDataReceived += OKDataReceived;
pServer.ErrorDataReceived += ErrorDataReceived;
pServer.Start();
pServer.BeginErrorReadLine();
pServer.BeginOutputReadLine();
}
private void ErrorDataReceived(object sender, DataReceivedEventArgs e)
=> Application.Current.Dispatcher.Invoke(() => ConsoleLines.Add($"ERROR: {e.Data}"));
private void OKDataReceived(object sender, DataReceivedEventArgs e)
=> Application.Current.Dispatcher.Invoke(() => ConsoleLines.Add($"MESSAGE: {e.Data}"));
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
As far as I know everything is setup correctly I can't see anything that is wrong.
And here is my XAML
<DataGrid ItemsSource="{Binding Server.ConsoleLines}" Width="400" Margin="182,109,210,68">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Name="BtnStart" Click="BtnStart_OnClick" Content="Start Server" HorizontalAlignment="Left" Margin="365,388,0,0" VerticalAlignment="Top" Width="75"/>
EDIT
Initializing a new instance of the server and then using the Button to start the method on a new instance seem to be the issue.. I dont know why though.

You should remove the Server field from your MainWindow and start the Server that is inside your DataContext.
private void BtnStart_OnClick(object sender, RoutedEventArgs e)
{
((MasterViewModel)DataContext).Server.StartServer();
}
Alternatively, initialize the view model like this:
DataContext = new MasterViewModel { Server = Server };
which assigns the Server field value to the Server property in your view model (which must then be read/write). Otherwise you've got two Server instances.

Related

C# Binding with DataContext and INotifyPropertyChanged

What am I doing wrong?
I have a Class Model.cs that has my DataContext
I have a Button and a TextBlock next to it. I have tried binding and implementing INotifyPropertyChanged.
When the button is clicked it calls a method that uses WinForms to look for a folder location and display it in the TextBlock
but it does not update. If I debug I get the path correctly.
Any help much appreciated.
MainWindow.xaml
<Button Name="projectLocationBtn"
Width="150"
Height="30"
Click="projectLocationBtn_Click">
<StackPanel Orientation="Horizontal">
<fa:FontAwesome Icon="FolderOpen" Margin="0 0 10 0" />
<TextBlock Text="Select Location" />
</StackPanel>
</Button>
<StackPanel Orientation="Horizontal" Margin="20 0 0 0">
<fa:FontAwesome Icon="AngleRight" Margin="0 0 10 0"/>
<TextBlock Width="800"
TextAlignment="Left"
TextWrapping="NoWrap"
Text="{Binding ProjectLocation}"/>
</StackPanel>
MainWindow.xaml.cs
using M = MercuryTemplateGenerator.Model;
public MainWindow()
{
InitializeComponent();
DataContext = new M.Model();
}
private void projectLocationBtn_Click(object sender, RoutedEventArgs e)
{
M.Model m = new M.Model();
m.GetLocation();
}
Model Class
using Winforms = System.Windows.Forms;
namespace MercuryTemplateGenerator.Model
{
public class Model: INotifyPropertyChanged
{
string _projectLocation;
string _projectName;
public Model() {}
public string ProjectName
{
get {
return _projectName; }
set {
_projectName = value;
OnPropertyChanged("ProjectName");
}
}
public string ProjectLocation
{
get {
return _projectLocation; }
set {
_projectLocation = value;
OnPropertyChanged("ProjectLocation");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(property));
}
public void GetLocation()
{
// get path to desktop
var startPath =
Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
Winforms.FolderBrowserDialog folderDialog = new
Winforms.FolderBrowserDialog();
folderDialog.ShowNewFolderButton = false;
folderDialog.SelectedPath = startPath;
Winforms.DialogResult pathResult = folderDialog.ShowDialog();
if (pathResult == Winforms.DialogResult.OK)
{
_projectLocation = folderDialog.SelectedPath;
}
}
}
}
Many thanks.
The mistake is you have one instance of Model for dataContext of the page and have another one instance you're calling inside projectLocationBtn_Click. If a view is bounded to dataContext it means it's special instance of class lays under view and view will get new data from there. You need to call GetLocation method on the same instance of Model. For example, you can save your first model to field.
_dataContext = new M.Model();
DataContext = _dataContext;
And then use this instance inside handler
private void projectLocationBtn_Click(object sender, RoutedEventArgs e)
{
_dataContext.GetLocation();
}
I can see that after all, it won't work because you don't call OnPropertyChanged("ProjectLocation").
For calling it you have to call setter of ProjectLocation property
Replace:
_projectLocation = folderDialog.SelectedPath;
with
ProjectLocation = folderDialog.SelectedPath;
And for your info: Check how can Button's click be bound to DataContext with Binding work inside XAML file.
https://www.codeproject.com/Articles/238657/How-to-use-Commands-in-WPF
In the GetLocation function you need to set the ProjectLocation property to raise the PropertyChanged event, if you set directly the _projectLocation private field the event won't be raised because it is inside the setter of the property

Bind TextBlock Value to Object

I've created my own socket class and an instance of it in MainWindow.xaml.cs, and I want to create a small little TextBlock to monitor the connection status. I've been using this specific link: WPF textblock binding in XAML
Here's the code attempt. ComUplink.cs:
public class ComUplink
{
public String ConnectionStatus = "Idle";
public Socket Socklink;
}
In MainWindow.xaml.cs:
public partial class MainWindow : Window
{
ComUpLink Uplink;
...
public void Login_Click(object Sender, RoutedEventArgs e)
{
Uplink = new ComUpLink();
}
}
AND in the XAML file:
<TextBlock x:Name="textBlock3"
TextAlignment="Right"
HorizontalAlignment="Left"
Margin="12,218,0,0"
TextWrapping="Wrap"
Text="{Binding Path=Uplink.ConnectionString}"
VerticalAlignment="Top"
Foreground="#616161"
Width="236"/>
So, my question is, why isn't this binding properly? Am I missing an implementation of INotifyPropertyChanged?
Well you made three little mistakes:
You can only bind to properties (if those values change use INotifyPropertyChanged)
You need to set the DataContext
Your Binding used the wrong property name (ConnectionString instead of ConnectionStatus)
Try those modifications:
in MainWindow.xaml.cs:
public void Login_Click(object Sender, RoutedEventArgs e)
{
this.DataContext = new ComUpLink();
}
in ComUplink.cs:
public class ComUplink : INotifyPropertyChanged
{
private String connectionStatus = "Idle";
public String ConnectionStatus
{
get
{
return this.connectionStatus;
}
set
{
this.connectionStatus = value;
this.OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Socket Socklink;
}
in MainWindow.xaml:
<TextBlock x:Name="textBlock3"
TextAlignment="Right"
HorizontalAlignment="Left"
Margin="12,218,0,0"
TextWrapping="Wrap"
Text="{Binding Path=ConnectionStatus}"
VerticalAlignment="Top"
Foreground="#616161"
Width="236"/>
You first need to set the data context of the text block to be the main window or a property.
Second you need to bind to a public property not to field

WPF Data bound UI element not updating

I have the following XAML:
<StackPanel Orientation="Vertical">
<TextBox IsReadOnly="True" Text="{Binding SchedulerStatus, Mode=OneWay}" Width="150" VerticalAlignment="Center" Margin="10" />
<Button Width="75" Height="30" Content="Test" Command="{Binding StartScheduler}" />
</StackPanel>
This is in a Window bound to this view model:
public class SchedulerViewModel : ViewModelBase // ViewModelBase implements INotifyPropertyChanged, using the [CallerMemberName] attribute.
{
private readonly SchedulerServiceClient _proxy;
public SchedulerViewModel()
{
_proxy = new SchedulerServiceClient();
SchedulerStatusPoller poller = new SchedulerStatusPoller(this, _proxy);
}
private SchedulerStatus _schedulerStatus;
internal SchedulerStatus SchedulerStatus
{
get
{
return _schedulerStatus;
}
set
{
if (value != _schedulerStatus)
{
_schedulerStatus = value;
OnPropertyChanged();
}
}
}
}
SchedulerServiceClient is a proxy to a WCF service that runs continually, and has a Status property that I need to watch. Because I cannot get callbacks from WCF to work after two solid days trying, I have implemented SchedulerStatusPoller, that periodically polls the WCF status, and updates the viewmodel status, in the hope that the display of the WCF status will be updated.
class SchedulerStatusPoller
{
private static readonly Timer StatusTimer = new Timer(5000);
private static SchedulerViewModel viewModel;
private static SchedulerServiceClient proxy;
public SchedulerStatusPoller(SchedulerViewModel targetViewModel, SchedulerServiceClient proxy)
{
SchedulerStatusPoller.proxy = proxy;
viewModel = targetViewModel;
StatusTimer.Elapsed += StatusTimerElapsed;
StatusTimer.AutoReset = true;
StatusTimer.Enabled = true;
StatusTimer.Start();
}
void StatusTimerElapsed(object sender, ElapsedEventArgs e)
{
viewModel.SchedulerStatus = proxy.GetStatus();
}
}
I have used the following code directly in the UI (the Window) that confirms that PropertyChanged is being raised by the SchedulerViewModel. The exception is thrown.
void _viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
((SchedulerViewModel)DataContext).SchedulerStatus = SchedulerStatus.Processing;
}
The poller does call into the SchedulerStatus property on the viewmodel every five seconds, but the textbox does not update. What am I doing wrong?
You have to mention the source trigger
Text="{Binding SchedulerStatus, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Your OnPropertyChanged inside the Setter is missing an argument. Shouldn't it be sth. like
PropertyChanged("SchedulerStatus");
maybe,you can code it like this:
OnPropertyChanged(this,new PropertyChangedEventArgs("SchedulerStatus"));

Add to Favorite a Button inside a StackPanel

I have a problem and nothing come to my mind how to solve this.
I need to add a button to Favorites in a StackPanel, the problem is that the button is pressed on other page that is StackPanel declared and I can't Add button to it's Children.
So, I have 2 pages: MainPage.xaml and PlayerPage.xaml .
In MainPage I keep a StackPanel with Buttons like:
<StackPanel x:Name="mainStack" >
<Button x:Name="but1" Click="Button2_Click" Tag="/videos/video1.mp4" Content="Play Video 1" />
<Button x:Name="but2" Click="Button2_Click" Tag="/videos/video2.mp4" Content="Play Video 2" />
<Button x:Name="but3" Click="Button2_Click" Tag="/videos/video3.mp4" Content="Play Video 3" />
</StackPanel>
<StackPanel x:Name="favoriteStack" >
<!-- Here need to be added favorite videos when user press Add to fav button! -->
</StackPanel>
.cs
private void Button2_Click(object sender, EventArgs e)
{
NavigationService.Navigate(
new Uri("/PlayerPage.xaml?path=" +
HttpUtility.UrlEncode((sender as Button).Tag.ToString()),
UriKind.Relative));
}
In PlayerPage.xaml :
<MediaElement x:Name="mediaElement1"
MediaOpened="mediaElement1_MediaOpened"
MediaFailed="mediaElement1_MediaFailed"
MediaEnded="mediaElement1_MediaEnded"
CurrentStateChanged="mediaElement1_CurrentStateChanged" />
<Button x:Name="AddToFav" Click="Button1_Click"
Content="Add this video to Favorites" />
.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (NavigationContext.QueryString.TryGetValue("path", out path))
{
if (!string.IsNullOrWhiteSpace(path))
{
mediaElement1.Source = new Uri( path );
}
}
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
// here must be somethink like:
Button butsender = new Button();
butsender = sender as Button;
stack2.Children.Add(butsender);
//better will be to save to Isolated Storage or somethink like this for future launching...
}
I have a lot of problems because I don't really get it how to perform that... I've tried to use global App Bar and allot more but unsuccesfully. Any help would be appreciated! Thanks!
The best way to do this is to follow the MVVM Pattern. You'll have a ViewModel class defined containing two lists.
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Video> Videos { get; set; }
public ObservableCollection<Video> FavoriteVideos { get; set; }
public MainViewModel()
{
Videos = new ObservableCollection<Video>();
FavoriteVideos = new ObservableCollection<Video>();
}
private Video selectedVideo;
public Video SelectedVideo
{
get { return selectedVideo; }
set { selectedVideo = value; NotifyPropertyChanged("SelectedVideo"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In App.xaml.cs you'll define the ViewModel
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
Then you can set the data context of both pages (MainPage and ther other) in the constructor like this:
DataContext = App.ViewModel;
Now you'll only have to bind the items to both lists (StackPanel won't do here. Use other listing like ListBox)
ItemsSource="{Binding Videos}"
ItemsSource="{Biding FavoriteVideos}"
For the navigation if you use ListBox just set the click event
ItemClick="ItemClick"
private void ItemClick(object sender, ItemClickEventArgs e)
{
Video item = e.ClickedItem as Video;
App.ViewModel.SelectedVideo = item;
NavigationService.Navigate(new Uri("/PlayerPage.xaml,UriKind.Relative));
}
At the PlayerPage just bind to SelectedVideo and you're good. For favorite you just have to add the video to the FavoriteList
private void FavoriteButton_Click(object sender, RoutedEventArgs e)
{
if(!App.ViewModel.FavoriteVideos.Contains(App.ViewModel.SelectedVideo)) {
App.ViewModel.FavoriteVideos.Add(App.ViewModel.SelectedVideo);
}
}
Now you can dynamically add data in your app if needed. StackPanel will only help with your static videos and it's not practical to use if the data changes.

Populate ListBox

I have a window with a textbox and a submit button. When pressing the submit button, the data in the textbox should populate into the listbox and be saved.
What's the best way of doing this? I tried a recommendation (using ObservableCollection) from an earlier question I had, but I can't seem to get it work. I have tried implementing it like this:
I created a class:
public class AccountCollection
{
private string accountName;
public string AccountName
{
get { return accountName; }
set { accountName = value; }
}
public AccountCollection(string accountName)
{
AccountName = accountName;
}
}
Assigned the binding in my XAML:
<ListBox ItemsSource="{Binding AccountName, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" SelectionChanged="accountListBox_SelectionChanged" />
...and finally, when a user clicks the submit button from another window that contains the Submit button and textbox:
private void okBtn_Click(object sender, RoutedEventArgs e)
{
BindingExpression expression = okBtn.GetBindingExpression(accountaddTextBox.Text);
expression.UpdateSource();
}
But alas, I'm getting nowhere. I get an error message at the GetBindingExpression section:
Argument 1: cannot convert from 'string' to 'System.Windows.DependencyProperty'
What's obvious to me here is that when I created the class I didn't specify anything about the account name from the textbox, so I don't even know if the class is correct.
I'm basically confused and don't know what to do. Any help would be appreciated...
MODEL
// the model is the basic design of an object containing properties
// and methods of that object. This is an account object.
public class Account : INotifyPropertyChanged
{
private string m_AccountName;
public event PropertyChangedEventHandler PropertyChanged;
public string AccountName
{
get { return m_AccountName;}
set
{
m_AccountName = value;
OnPropertyChanged("AccountName");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ListBox XAML
<ListBox Name="MyAccounts" DisplayMemberPath="AccountName" />
CODE BEHIND
// create a collection of accounts, then whenever the button is clicked,
//create a new account object and add to the collection.
public partial class Window1 : Window
{
private ObservableCollection<Account> AccountList = new ObservableCollection<Account>();
public Window1()
{
InitializeComponent();
AccountList.Add(new Account{ AccountName = "My Account" });
this.MyAccounts.ItemsSource = AccountList;
}
private void okBtn_Click(object sender, RoutedEventArgs e)
{
AccountList.Add(new Account{ AccountName = accountaddTextBox.Text});
}
}
edit: added displaymemberpath on listbox xaml
Here is a Demo using MVVM approach
ViewModel
public class AccountListViewModel : INotifyPropertyChanged
{
ICommand AddAccountCommand {get; set;}
public AccountListViewModel()
{
AccountList = new ObservableCollection<string>();
AddAccountCommand= new RelayCommand(AddAccount);
//Fill account List saved data
FillAccountList();
}
public AddAccount(object obj)
{
AccountList.Add(AccountName);
//Call you Model function To Save you lIst to DB or XML or Where you Like
SaveAccountList()
}
public ObservableCollection<string> AccountList
{
get {return accountList} ;
set
{
accountList= value
OnPropertyChanged("AccountList");
}
}
public string AccountName
{
get {return accountName } ;
set
{
accountName = value
OnPropertyChanged("AccountName");
}
}
}
Xaml Binding
<ListBox ItemsSource="{Binding Path=AccountList}" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" />
<TextBox Text={Binding Path=AccountName}></TextBox>
<Button Command={Binding Path=AddAccountCommand}><Button>
Xaml.cs Code
# region Constructor
/// <summary>
/// Default Constructor
/// </summary>
public MainView()
{
InitializeComponent();
this.DataContext = new AccountListViewModel();
}
# endregion
The Implementation of INotifyPropertyChanged and forming porpeties is left upto you
Your ItemsSource for your ListBox is AccountName, which is only a string but not a collection.
You need to create a viewmodel (your datacontext for the view) like this:
public class ViewModel
{
public ViewModel()
{
Accounts = new ObservableCollection<string>();
}
public ObservableCollection<string> Accounts { get; set; }
}
Bind ItemsSource to Accounts property:
<ListBox ItemsSource="{Binding Accounts}" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" />
And then, in your click event handler of the button you can simple add the current value of the textbox to your collection:
private void okBtn_Click(object sender, RoutedEventArgs e)
{
Accounts.Add(accountaddTextBox.Text);
}
But don't forget to set the DataContext of your window to the class ViewModel.

Categories

Resources