here is the problem i am encountering, i will use the class names in my demo to describe my problem:
i have to show something on a datagrid, as a view of Item. but the Item itself is a wrapper class of the real data source, Dummy.
Container is the manager of all Dummy's, it will call Poll periodically(in my demo, 1s) and it internally queries the remote server to decide the current value(in demo, i simply flip the boolean). and after that, Container will go through all Item's and Notify them changes may be made.
but my problem is, the if branch in Notify called by Container won't get in, since PropertyChanged is null! but if i change it in the DataGrid, i.e. dg, by double click, this event gets subscribed. so my problem is, how can i successfully notify in Container? is there any a way to make DataGrid subscribe to PropertyChanged all the time?
btw, if i use a debugger to get in, they are null too.
Following is my demo code:
xaml:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg" Margin="0,0,0,35"/>
<Button Content="Button" Margin="0,0,10,10" Height="20" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="75" Click="Button_Click"/>
</Grid>
</Window>
c# part:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace WpfApplication6
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Container _container;
public MainWindow()
{
InitializeComponent();
_container = new Container();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
dg.ItemsSource = _container.GetItems();
}
}
public class Dummy
{
public Dummy()
{
_done=false;
}
private bool _done;
public bool Done {
get { return _done; }
// set will trigger something remotely too.
set { _done = value; }
}
public void Poll() { _done=!_done;}
}
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Dummy dummy;
public Item(Dummy dum)
{
dummy = dum;
}
public bool Done
{
get { return dummy.Done; }
set
{
dummy.Done = value;
Notify();
}
}
public void Notify()
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Done"));
}
}
public class Container
{
private List<Dummy> dummies;
private DispatcherTimer _updateTimer;
public Container()
{
dummies = (from i in Enumerable.Range(0, 10)
select new Dummy()).ToList();
}
private IEnumerable<Item> items;
public Item[] GetItems()
{
if (_updateTimer != null)
_updateTimer.Stop();
items=dummies.Select(x => new Item(x));
_updateTimer = new DispatcherTimer();
_updateTimer.Interval = TimeSpan.FromSeconds(1);
_updateTimer.Tick += (_1, _2) =>
{
foreach (var item in dummies)
{
item.Poll();
}
foreach (var item in items)
{
item.Notify();
}
};
_updateTimer.Start();
return items.ToArray();
}
}
}
Related
I am working on a to-do list application for a project. I would like to change the value of a string in an observableCollection. I am able to change the string in the same window but I would like to change the value from a textbox in a secondary window.
So what I tried to do was is change a string in the first window by using a textbox in the second window. By doing the way I have listed below it just blanks out the item I am trying to edit.
I would like to take the test from the textbox in the second window and use it to modify the taskName in the first window. Below I am going to include my code for the two c# files for the windows.
This is the main window but it is called DemoMainWindow:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using ToDoList.ViewModels;
using ToDoList.Model;
namespace ToDoList
{
/// <summary>
/// Interaction logic for DemoMainWindow.xaml
/// </summary>
public partial class DemoMainWindow : Window
{
private ViewModel _viewModel;
public string? EditedTaskName { get; set; }
public DemoMainWindow()
{
InitializeComponent();
TxtUCEnteredTask.txtLimitedInput.Text = "Do the dishes";
_viewModel = new ViewModel();
DataContext = _viewModel;
}
private void BtnAddTask_Click(object sender, RoutedEventArgs e)
{
_viewModel.Tasks.Add(new TaskModel() { TaskName = TxtUCEnteredTask.txtLimitedInput.Text });
}
private void BtnDeleteTask_Click(object sender, RoutedEventArgs e)
{
if(LstBoxTasks.SelectedItem != null)
{
#pragma warning disable CS8604 // Possible null reference argument.
_ = _viewModel.Tasks.Remove(item: LstBoxTasks.SelectedItem as TaskModel);
#pragma warning restore CS8604 // Possible null reference argument.
}
}
private void BtnHelp_Click(object sender, RoutedEventArgs e)
{
HelpWindow helpWindow = new HelpWindow();
helpWindow.Show();
}
private string? GetEditedTaskName()
{
return EditedTaskName;
}
private void BtnEditTask_Click(object sender, RoutedEventArgs e)
{
if (LstBoxTasks.SelectedItem != null)
{
EditWindow editWindow = new EditWindow();
editWindow.Show();
//_viewModel.Tasks[LstBoxTasks.SelectedIndex].TaskName = editedTaskName;
}
}
}
}
This is the code for the C# file of the second window:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace ToDoList
{
/// <summary>
/// Interaction logic for EditWindow.xaml
/// </summary>
public partial class EditWindow : Window
{
public EditWindow()
{
InitializeComponent();
var DemoMainWindow = this.DataContext;
}
private void BtnEdit_Click(object sender, RoutedEventArgs e)
{
((DemoMainWindow)Application.Current.MainWindow).EditedTaskName = EditTextBox.Text;
// _viewModel.Tasks[LstBoxTasks.SelectedIndex].TaskName = TxtUCEnteredTask.txtLimitedInput.Text;
}
}
}
When you create the view, create it's viewmodel before so that you can set the view's datacontext:
EditWindowViewModel vm = new EditWindowViewModel();
EditWindow editWindow = new EditWindow()
{
DataContext = vm;
};
editWindow.Show();
StringToChange = vm.EditBoxTextProperty;
Create a bindable property for stroing the editbox's text using INotifyPropertyCganged in the EditWindowViewModel (see this):
private string _editBoxTextProperty;
public string EditBoxTextProperty
{
get => _editBoxTextProperty;
set
{
if (_editBoxTextProperty != value)
{
_editBoxTextProperty = value;
OnPropertyChanged();
}
}
}
In the EditWindow xaml, use binding to connect the editbox's text value to your EditBoxTextProperty.
<TextBox Text="{Binding Path=EditBoxTextProperty}"/>
Usefull links to build a proper WPF application: DataBinding MVVM Pattern
What I have found to be useful when having to interact between elements on different windows is using x:FieldModifier
<TextBox x:Name="textbox" x:FieldModifier="public" />
You can then access the element through the instance of you window
it will appear as WindowInstanceName.textbox
I want to execute a method on TextChange event and for specific text, I want to do something and close the window using MVVM
for example on this part of my code I want to close my window:
if (text.Equals("12345"))
{
//Exit from window
}
I know how can I do it from a button using a command, As I have in the code of the next example.
But how can I pass the window to a running property that binds to a text of the textbox?
or there is another way to close form?
I want to do it on the ViewModel and not the code behind to write my code as MVVM pattern close that I can
my XAML code :
<Window x:Class="PulserTesterMultipleHeads.UserControls.TestWindows"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PulserTesterMultipleHeads.UserControls"
mc:Ignorable="d"
Name="MainTestWindow"
Title="TestWindows" Height="450" Width="800">
<Grid>
<StackPanel>
<TextBox Text="{Binding Text,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Button Command="{Binding EndTestExit}"
CommandParameter="{Binding ElementName=MainTestWindow}">Exit</Button>
</StackPanel>
</Grid>
</Window>
the code behind XAML :
using PulserTesterMultipleHeads.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace PulserTesterMultipleHeads.UserControls
{
/// <summary>
/// Interaction logic for TestWindows.xaml
/// </summary>
public partial class TestWindows : Window
{
public TestWindows()
{
InitializeComponent();
DataContext = new TestWindowsMV();
}
}
}
View Model code :
using PulserTester.ViewModel.Base;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace PulserTesterMultipleHeads.Classes
{
public class TestWindowsMV : INotifyPropertyChanged
{
private string text;
public string Text {
get {
return text;
} set {
text = value;
if (text.Equals("12345"))
{
//Exit from window
}
}
}
/// <summary>
/// for the example
/// </summary>
private ICommand _EndTestExit;
public ICommand EndTestExit
{
get
{
if (_EndTestExit == null)
{
_EndTestExit = new GenericRelayCommand<Window>((window) => EndTestExitAction(window));
}
return _EndTestExit;
}
}
private void EndTestExitAction(Window window)
{
window.Close();
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
************************* Edit as the answer ****************************
The change in ModelView :
public string Text {
get {
return text;
} set {
text = value;
if (text.Equals("12345"))
{
CloseAction();
}
}
}
The change in code behind:
public TestWindows()
{
InitializeComponent();
DataContext = new TestWindowsMV();
if (((TestWindowsMV)DataContext).CloseAction == null)
((TestWindowsMV)DataContext).CloseAction = new Action(this.Close);
}
It is kind of hard with MVVM but what you can do is create an Event inside of your ViewModel and attach a function that will close your window in code behind of your View. Then you will be able to call your event inside of ViewModel whenever you need to close the window (or do something else that is more convenient to do in View rather than in ViewModel)
The cleanest way to close a View from the ViewModel is to use an attached property
public static class perWindowHelper
{
public static readonly DependencyProperty CloseWindowProperty = DependencyProperty.RegisterAttached(
"CloseWindow",
typeof(bool?),
typeof(perWindowHelper),
new PropertyMetadata(null, OnCloseWindowChanged));
private static void OnCloseWindowChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
if (!(target is Window view))
return;
if (view.IsModal())
view.DialogResult = args.NewValue as bool?;
else
view.Close();
}
public static void SetCloseWindow(Window target, bool? value)
{
target.SetValue(CloseWindowProperty, value);
}
public static bool IsModal(this Window window)
{
var fieldInfo = typeof(Window).GetField("_showingAsDialog", BindingFlags.Instance | BindingFlags.NonPublic);
return fieldInfo != null && (bool)fieldInfo.GetValue(window);
}
}
which you can then bind to an appropriate property in your ViewModel
<Window
x:Class="...
vhelp:perWindowHelper.CloseWindow="{Binding ViewClosed}">
private bool? _viewClosed;
public bool? ViewClosed
{
get { return _viewClosed; }
set { Set(nameof(ViewClosed), ref _viewClosed, value); }
}
More details on my recent blog post.
You have almost everything you need already implemented. All you really need to do is call private void EndTestExitAction(Window window) from your setter and supply window its value during construction:
public string Text {
get {
return text;
} set {
text = value;
if (text.Equals("12345"))
{
EndTestExitAction(window)
}
}
}
where window is a property of your View Model.
I'm working on a C# WPF project in Visual Studio in which I allow the user to add a user control (with two text boxes in the code below), which creates a class (with data stored within the user control so that multiple user controls can be created with a single button) and adds it to a list stored in the main window file to be written to a csv file later. I'm getting the following error when I hit the button to create a new user control:
An unhandled exception of type 'System.NullReferenceException' occurred in MasterListErrorTest.exe
Additional information: Object reference not set to an instance of an object.
I created a simplified version of my project that contains just the elements needed to reproduce the error. Here's all my code so you can plug it straight in and get the error for yourself. What am I doing wrong?
MainWindow.xaml:
<Window x:Class="MasterListErrorTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MasterListErrorTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel x:Name="UserControlContainer" HorizontalAlignment="Left" Height="216" Margin="24,83,0,0" VerticalAlignment="Top" Width="471" Orientation="Horizontal"/>
<Button x:Name="CreateNewControl" Content="Create New" HorizontalAlignment="Left" Margin="76,37,0,0" VerticalAlignment="Top" Width="75" Click="CreateNewControl_Click"/>
<Button x:Name="GiveStringFromList" Content="Give String" HorizontalAlignment="Left" Margin="360,37,0,0" VerticalAlignment="Top" Width="75" Click="GiveStringFromList_Click"/>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MasterListErrorTest
{
public partial class MainWindow : Window
{
int MultipleTextBoxControlID = 1;
public MainWindow()
{
InitializeComponent();
}
public static class TextBoxControlList
{
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;
}
private void CreateNewControl_Click(object sender, RoutedEventArgs e)
{
MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID);
UserControlContainer.Children.Add(newUserControl);
}
private void GiveStringFromList_Click(object sender, RoutedEventArgs e)
{
foreach (MultipleTextBoxControl.TextBoxData textBoxPanel in TextBoxControlList.MasterDataList)
{
List<string> userControlLine = new List<string>();
userControlLine.Add(textBoxPanel.Identifier.ToString());
userControlLine.Add(textBoxPanel.TextBox1Data);
userControlLine.Add(textBoxPanel.TextBox2Data);
MessageBox.Show(string.Join(",", userControlLine));
}
}
}
}
MultipleTextBoxControl.xaml:
<UserControl x:Class="MasterListErrorTest.MultipleTextBoxControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MasterListErrorTest"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="300">
<Grid>
<TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="23" Margin="0,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" TextChanged="textBox1_TextChanged"/>
<TextBox x:Name="textBox2" HorizontalAlignment="Left" Height="23" Margin="153,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" TextChanged="textBox2_TextChanged"/>
</Grid>
</UserControl>
MultipleTextBoxControl.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MasterListErrorTest
{
public partial class MultipleTextBoxControl : UserControl
{
TextBoxData newTextBoxGroup = new TextBoxData();
public MultipleTextBoxControl(int identifier)
{
InitializeComponent();
newTextBoxGroup.Identifier = identifier;
MainWindow.TextBoxControlList.MasterDataList.Add(newTextBoxGroup);
}
public class TextBoxData
{
public int Identifier { get; set; }
public string TextBox1Data { get; set; }
public string TextBox2Data { get; set; }
public TextBoxData()
{
TextBox1Data = "Unchanged Textbox 1";
TextBox2Data = "Unchanged Textbox 2";
}
}
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
newTextBoxGroup.TextBox1Data = textBox1.Text;
}
private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
{
newTextBoxGroup.TextBox2Data = textBox2.Text;
}
}
}
Change your TextBoxControlList class to :
public static class TextBoxControlList
{
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;
static TextBoxControlList() {
MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
}
}
A better way to do :
Refactor#1
MultipleTextBoxControl.xaml.cs
public partial class MultipleTextBoxControl : UserControl
{
TextBoxData _newTextBoxGroup;
public TextBoxData TextBoxGroup { get { return _newTextBoxGroup; } }
public MultipleTextBoxControl(int identifier)
{
InitializeComponent();
_newTextBoxGroup = new TextBoxData(identifier);
}
public class TextBoxData
{
public int Identifier { get; set; }
public string TextBox1Data { get; set; }
public string TextBox2Data { get; set; }
public TextBoxData(int identifier)
{
Identifier = identifier;
TextBox1Data = "Unchanged Textbox 1";
TextBox2Data = "Unchanged Textbox 2";
}
}
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
_newTextBoxGroup.TextBox1Data = textBox1.Text;
}
private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
{
_newTextBoxGroup.TextBox2Data = textBox2.Text;
}
}
MainWindow.cs
public partial class MainWindow : Window
{
int MultipleTextBoxControlID = 1;
public static List<MultipleTextBoxControl.TextBoxData> MasterDataList;
static MainWindow() {
MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
}
public MainWindow()
{
InitializeComponent();
}
private void CreateNewControl_Click(object sender, RoutedEventArgs e)
{
MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID);
UserControlContainer.Children.Add(newUserControl);
MasterDataList.Add(newUserControl.TextBoxGroup);
}
private void GiveStringFromList_Click(object sender, RoutedEventArgs e)
{
foreach (MultipleTextBoxControl.TextBoxData textBoxPanel in MasterDataList)
{
List<string> userControlLine = new List<string>();
userControlLine.Add(textBoxPanel.Identifier.ToString());
userControlLine.Add(textBoxPanel.TextBox1Data);
userControlLine.Add(textBoxPanel.TextBox2Data);
MessageBox.Show(string.Join(",", userControlLine));
}
}
}
If you put a try/catch exception block around the code in your CreateNewControl_Click then the caught exception will give you more information about what's going on. The StackTrace says this:
at GuiTest.MultipleTextBoxControl..ctor(Int32 identifier) in c:\Dev\GuiTest\MultipleTextBoxControl.xaml.cs:line 31
at GuiTest.MainWindow25.CreateNewControl_Click(Object sender, RoutedEventArgs e) in c:\Dev\GuiTest\MainWindow25.xaml.cs:line 43
So the most recent item on the list is MultipleTextBoxControl.xaml.cs line 31:
MainWindow25.TextBoxControlList.MasterDataList.Add(newTextBoxGroup);
Placing a breakpoint here and examining the contents reveals that MasterDataList is null, because you're not initializing it in TextBoxControlList:
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;
So do so:
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
Personally I would strongly advise against the use of static classes members, especially in cases like this, but either way this is the answer to your question.
EDIT:
I totally agree with the main points that AnjumSKhan is making, although I personally would go about it using Inversion of Control (IoC). What you're really trying to do is get the child controls to register their data in a way that the Main Window code can access later. As AnjumSKhan points out the children shouldn't know anything about their parent, but you should also be able to create and unit test this behaviour in your child class without needing to create a parent. Inversion of control involves passing in an interface to the child that it should use to register itself, a very simple example might be this:
public interface IDataRegistrationService
{
void Register(MultipleTextBoxControl.TextBoxData data);
}
Your child window can accept a reference to such a service in its constructor and do the registration as it was before but using this service instead:
public MultipleTextBoxControl(int identifier, IDataRegistrationService service)
{
InitializeComponent();
newTextBoxGroup.Identifier = identifier;
service.Register(newTextBoxGroup); // <---------
}
Your MainWindow class can now inherit from this and pass a reference to itself in when the child is created (note I've also made MasterDataList a regular property of MainWindow):
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
public void Register(MultipleTextBoxControl.TextBoxData data)
{
MasterDataList.Add(data);
}
private void CreateNewControl_Click(object sender, RoutedEventArgs e)
{
MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID, this);
UserControlContainer.Children.Add(newUserControl);
}
By doing this you've formalized the relationship between the parent and child and prevented yourself or others adding more relationships between the two that might be difficult to untangle or change later. You're also now in a position where you can create an instance of your child and use a mocked object (e.g. using the Moq library) to test that it behaves as expected. And by maintaining good separation of concerns you have the freedom to pass in any service you want...maybe later on you'll decide that you need multiple panels with one server per panel.
The one downside to IoC is that you wind up passing references to service provider all over your project, with middle layers keeping references to objects higher up the hierarchy for the sole purpose of passing them lower down. This is what Dependency Injection frameworks solve (e.g. Ninject). They get rid of all that parameter passing and your final code winds up looking something like this:
public partial class MultipleTextBoxControl : UserControl
{
// this gets created by the DI framework, with identifier set automatically
[Inject] private TextBoxData newTextBoxGroup { get; set; }
// this get injected automatically when the class is create
[Inject] private IDataRegistrationService DataService {get; set;}
public MultipleTextBoxControl()
{
InitializeComponent();
}
// this gets called immediately after the constructor
public void Initialize()
{
// and you do any custom initialization here, using injected components
this.DataService.Register(newTextBoxGroup);
}
I used semiglobal reference to MainWindow in user control like this:
public partial class MainWindow : Window
{
public static MainWindow Me = null;
public MainWindow()
{
Me = this;
InitializeComponent();
Then I used this "Me" in User control:
public partial class UserControlTable : UserControl
{
DataCenterSender m_DataCenterSender = new DataCenterSender(MainWindow.Me.Get_DataCenter());
And had a Null Pointer in XAML Editor.
Looks like constructor of MainWindow has not been called in XAML editor and since MainWindow.Me==null in this case User Control ctor failed.
I am trying to read from a serial port and put the data into a grid.
and the data doesn't appear in the data grid.
I used notify changer per martin comment below.
only wanted to show the data on the datagrid view when serial port is updated.
here is the xaml:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="689.7" Width="655">
<Grid Margin="0,0,2,9">
<DataGrid x:Name="DataGridData" Margin="50,25,19,10" AutoGenerateColumns="False" ItemsSource="{Binding Source=FirstName}">
</DataGrid>
</Grid>
</Window>
and here is the CS:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO.Ports;
using System.Threading;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
/// <summary>
/// this class contain a row for the data to show in the dataGridRecieved
/// </summary>
public class DataForTable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string time; // contain the time of the data that has been sent or recieved.
/// <summary>
/// get or sets the time that has been recieved.
/// </summary>
public string Time
{
get { return time; }
set { time = value;
PropertyChanged(this, new PropertyChangedEventArgs("Time"));
}
}
private string data; // contain the line of data the has been recieved.
/// <summary>
/// get or set the line of data that has been send to recieved
/// </summary>
public string Data
{
get { return data; }
set { data = value;
PropertyChanged(this, new PropertyChangedEventArgs("Data"));
}
}
}
private System.Threading.Thread threadSerial;
private System.IO.Ports.SerialPort _sp;
private Queue<byte[]> _QbytesFromSerialPort1;
private Queue<DateTime> _QDateTimeFromSerialPort1;
private byte[] _RealBytesFromSerialPort;
private delegate void emptydelagte(); // delegate to pass on the play option.
private emptydelagte updateTableGuiDelgate;
public DataForTable _dataForTable;
private Thread GUIThread;
object lockMessage;
private ObservableCollection <DataForTable> _firstName;
public ObservableCollection<DataForTable> FirstName
{
get { return _firstName; }
set { _firstName = value;
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
}
}
public MainWindow()
{
InitializeComponent();
_QbytesFromSerialPort1 = new Queue<byte[]>();
_QDateTimeFromSerialPort1 = new Queue<DateTime>();
_firstName = new ObservableCollection<DataForTable>();
_dataForTable = new DataForTable();
// DataGridData.ItemsSource = _firstName.Select(DataForTable => new { DataForTable.Data, DataForTable.Time }).ToList();
//create serial port
_sp = new SerialPort("COM1", 38400, Parity.None, 8, StopBits.One);
_sp.Open();
threadSerial = new Thread(new ThreadStart(readSerialPort)); //reads from serial port.
threadSerial.IsBackground = true;
threadSerial.Start();
//updateTableGuiDelgate = new emptydelagte(updateTableGuiDelgate);
GUIThread = new Thread(new ThreadStart(GuIThread));
GUIThread.IsBackground = true;
GUIThread.Start();
}
private void readSerialPort()
{
_RealBytesFromSerialPort = new byte[100];
DateTime _dtRead = new DateTime();
lockMessage = 0;
while (true)
{
if (_sp.IsOpen)
{
if (_sp.BytesToRead > 0)
{
lock (lockMessage)
{
_RealBytesFromSerialPort = new byte[_sp.BytesToRead];
_sp.Read(_RealBytesFromSerialPort, 0, _sp.BytesToRead); //read data
if (_RealBytesFromSerialPort.Length != 0)
{
_dtRead = DateTime.Now;
_QbytesFromSerialPort1.Enqueue(_RealBytesFromSerialPort); // q from serial porta
_QDateTimeFromSerialPort1.Enqueue(_dtRead); // q from date time now.
}
}
}
}
}
}
private byte[] tmp;
private void GuIThread()
{
_dataForTable = new DataForTable();
while (true)
{
if (_QbytesFromSerialPort1.Count > 0)
{
_dataForTable.Data = byteToString(_QbytesFromSerialPort1.Dequeue());
_dataForTable.Time = DateTime.Now.ToLongDateString();
_firstName.Add(_dataForTable);
// this.Dispatcher.BeginInvoke(updateTableGuiDelgate);
}
}
}
private string byteToString(byte [] arr)
{
string tmps = "";
for (int i = 0; i < arr.Length; i++)
{
tmps += arr[i].ToString() + " ";
}
return tmps;
}
}
}
Here is an example of how to add data from another thread:
<Window x:Class="DatagridBInding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding Items}"/>
</Grid>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<DataTableItem> _Items;
public ObservableCollection<DataTableItem> Items
{
get { return _Items; }
set
{
_Items = value;
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Items = new ObservableCollection<DataTableItem>();
Task.Factory.StartNew(() =>
{
Dispatcher.Invoke(() =>
{
for (int i = 0; i < 10; i++)
Items.Add(new DataTableItem() { Data = "Data " + i });
}
);
});
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
And the DataTableItem class:
public class DataTableItem : INotifyPropertyChanged
{
private string _Data;
public string Data
{
get { return _Data; }
set
{
_Data = value;
PropertyChanged(this, new PropertyChangedEventArgs("Data"));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
In order for the UI to be informed of newly items added, you need to use the ObservableCollection instead of that simple List.
One more thing, SerialPort class has a DataReceived event, wouldn't that be more suited here instead of polling ?
Further more, try to simplify the way you are going back to the GUI thread, it's a bit difficult to follow and it shouldn't be. In my example i have used a lambda expression on that Invoke. Check it out!
I'm working on a small WPF application in which I have a combobox that is bound to an ObservableCollection in the code behind:
public Molecule CurrentMolecule { get; set; }
public ObservableCollection<string> Formulas { get; set; }
public MainWindow()
{
CurrentMolecule = new Molecule();
Formulas = new ObservableCollection<string>(CurrentMolecule.FormulasList.ToList());
DataContext = this;
InitializeComponent();
}
<ComboBox x:Name="cmbFormula" ItemsSource="{Binding Path=Formulas}" SelectionChanged="cmbFormula_SelectionChanged"/>
This works fine to populate my combo box with the CurrentMolecule.FormulasList however if at some point I set CurrentMolecule to a new instance of Molecule the databinding no longer works. Do I need to implement some kind of OnPropertyChanged event so that no matter what the contents of the combo box will stay current with the CurrentMolecule.FormulasList?
You have to implement INotifyPropertyChanged, only then the changes will be updated in UI.
Here are the modifications that I've done to your code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Molecule _CurrentMolecule;
public Molecule CurrentMolecule
{
get
{
return _CurrentMolecule;
}
set
{
_CurrentMolecule = value;
OnPropertyChanged("CurrentMolecule");
Formulas = new ObservableCollection<string>(CurrentMolecule.FormulasList.ToList());
}
}
private ObservableCollection<string> _Formulas;
public ObservableCollection<string> Formulas
{
get { return _Formulas; }
set
{
_Formulas = value;
OnPropertyChanged("Formulas");
}
}
public MainWindow()
{
InitializeComponent();
CurrentMolecule = new Molecule();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Edit:
A better approach is to create a ViewModel and then bind it to the DataContext of the Window.
Define a new class called ViewModel as below. Note you might want to change the namespace
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged
{
#region Properties
private Molecule _CurrentMolecule;
public Molecule CurrentMolecule
{
get
{
return _CurrentMolecule;
}
set
{
_CurrentMolecule = value;
OnPropertyChanged("CurrentMolecule");
Formulas = new ObservableCollection<string>(CurrentMolecule.FormulasList.ToList());
}
}
private ObservableCollection<string> _Formulas;
public ObservableCollection<string> Formulas
{
get { return _Formulas; }
set
{
_Formulas = value;
OnPropertyChanged("Formulas");
}
}
#endregion
#region Constructor
public ViewModel()
{
CurrentMolecule = new Molecule();
}
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Modify the MainWindow code behind file as below
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
You are probably missing WPF controls datacontext fundamentals. If you are using binding like {Binding CurrentMolecule.FormulasList} or parent controls datacontext is bound to "CurrentMolecule" whenever you swap DataContext of that item, the binding will reset. If you would like to keep the datacontext bound to the FormulasList even when the parent datacontext changes. You need to bind that context directly to FormulasList property and make sure that other parent controls are not using CurrentMolecule as a datacontext, even when its instance changes in your ViewModel.