I have a list box that is databound to a collection from the entity frame work.
I need to find a way to update this listbox in the main window when a new object is added using another window. I see in the entity model, there is
protected override sealed void ReportPropertyChanging(string property);
but i don't know how to use this, or even if this is what it is for.
Here is my Main Window C#
public partial class MainWindow : Window
{
List<Game> _GameColletion = new List<Game>();
GameDBEntities _entity = new GameDBEntities();
public MainWindow()
{
InitializeComponent();
_GameColletion = _entity.Games.ToList<Game>();
DataContext = _GameColletion;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var newWindow = new AddGame();
newWindow.Show();
}
}
here is the list box xaml
<ListBox ItemsSource="{Binding}" Name="GameList"></ListBox>
And Finally Here is the code from another window that inserts a new Game into the Entity.
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
_newGame.Date = date.SelectedDate.Value;
_newGame.Time = time.Text;
MainWindow w = new MainWindow();
w._entity.AddToGames(_newGame);
w._entity.SaveChanges();
this.Close();
}
catch (Exception ex) { }
}
I just need that listBox to refresh when ever anything is added to or changed in the entity.
Thanks!
UPDATE: Here is what I have based on the posts, it still is not working
ObservableCollection<Game> _GameColletion;
GameDBEntities _entity = new GameDBEntities();
public MainWindow()
{
InitializeComponent();
DataContext = GameCollection;
}
public ObservableCollection<Game> GameCollection
{
get
{
if (_GameColletion == null)
{
_GameColletion = new ObservableCollection<Game>(_entity.Games.ToList<Game>());
}
return _GameColletion;
}
}
looks like you are using a List<Game> as your backing collection. In WPF, if you want notifications when the collection has items added or removed, use ObservableCollection<Game> instead (MSDN documentation here). Now, that said, it should be noted that only add/remove events are watched. There is NO notification on the individual objects that are being held in the collection. so, for example, if a property changes on your Game object that is held in the collection (say, a Score) property, your UI will NOT be notified. If you want this behavior, you can sub-class the ObservableCollection to add this behavior.
the ObservableCollection uses INotifyCollectionChanged(MSDN documentation here) interface, which the ListBox, ItemsControl, etc. respond to.
EDIT
ok, i see what is going on now... in addition to the changes above, and the changes to your getter, when you make a new game on the other window, it is getting added to the entity collection, but NOT to the observable collection. you need to add it to your observable collection. what you need to do to pass the OC to the child window is set ITS DataContext to your observable collection...
private void Button_Click(object sender, RoutedEventArgs e)
{
var newWindow = new AddGame();
newWindow.DataContext = this.DataContext; // ----> new code to set the DC
newWindow.Show();
}
// in the other window...
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
_newGame.Date = date.SelectedDate.Value;
_newGame.Time = time.Text;
((ObservableCollection<Game>)DataContext).Add( _newGame ); // ----> new code
entity.AddToGames(_newGame);
entity.SaveChanges();
this.Close();
}
catch (Exception ex) { }
}
Related
I have a WPF application which shows a Folder's contents in a Treeview in the MainWindowView. Now I have a new window where the user can change the location and press reload. Now I want to update the treeview in my MainWindowView as soon as the user presses the Reload button.
I am using an ObservableCollection object which is binded to the treeview. But I am not able to update the collection from the Change location window.
I want to know how to update the ObservableCollection of the MainWindowView from a different window. If I am doing any changes in the MainWindowView, then it immediately reflects in the TreeView
I am using MVVM architecture.
Is there any relationship between the MainWindow and the ChangeLocationWindow?
How does the ChangeLocationWindow show out, Show() or ShowDialog()? Check the following solution, any problems, let me know.
MainWindowViewModel:
public class MainWindowViewModel
{
public static MainWindowViewModel Instance = new MainWindowViewModel();
public ObservableCollection<string> Contents = new ObservableCollection<string>();
public string Location
{
get { return _location; }
set
{
if (_location != value)
{
_location = value;
ReloadContents();
}
}
}
private MainWindowViewModel()
{
}
private void ReloadContents()
{
// fill test data
Contents.Add("Some test data.");
}
private string _location;
}
MainWindowView:
{
public MainWindowView()
{
InitializeComponent();
MyListBox.ItemsSource = MainWindowViewModel.Instance.Contents;
var changeLocationWindow = new ChangeLocationWindow();
changeLocationWindow.Show();
}
}
ChangeLocationWindow:
public partial class ChangeLocationWindow : Window
{
public ChangeLocationWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MainWindowViewModel.Instance.Location = "Test";
}
}
The best approach to your problem is using Messaging pattern to send notifications to main viewmodel from another one about new changes.
Checkout the link for more details,
Scenario:
Three forms: MainWindow, Positions, Calibration self-named (MainWindow : Window etc.)
MainWindow creates an instance of three objects:
Vars: Model Vars = new Model();
Positions: Positions PositionsWindow = new Positions();
Calibration: Calibration CalibrationWindow = new Calibration();
A button in MainWindow is used to Show and Hide the child windows. Form fields in MainWindow update fields in class Vars.
MainWindow code:
public partial class MainWindow : Window
{
Model Vars = new Model();
Positions PositionsWindow = new Positions();
Calibration CalibrationWindow = new Calibration();
private void OpenButton_Click(object sender, RoutedEventArgs e)
{
PositionsWindow.Show();
}
private void TextBoxUpdate_Click(object sender, RoutedEventArgs e)
{
Vars.TestVar = TestBox.Text;
}
}
Question: How can form fields in the child windows update the parent form fields and/or fields in the class "Vars", i.e. passing data from child to parent and trigger an action in the parent form?
Attempts: A similar question suggested passing the main window this, example: Positions PositionsWindow = new Positions(); however, this only works when the object is created in a method. At this point, PositionsWindow.Show(); is no longer valid. i.e. it is only suitable for a child window created and closed in a single method.
I would not really recommend initializing the variables before the constructor. Don't get used to that.
I would change the constructor of each of the three Windows:
public partial class Model : Window{
MainWindow MW;
Model(MainWindow MW){
this.MW = MW;
// other constructor stuff
}
}
Do the same thing for Positions and Calibration.
Obviously, you cannot use this when you are INITIALIZING the Windows BEFORE the constructor is called, because there is still no this to pass.
Therefore, in your MainWindow:
public partial class MainWindow : Window
{
Model Vars; // = new Model(this); <- the constructor was not yet called, there is no this
Positions PositionsWindow; // = new Positions();
Calibration CalibrationWindow; // = new Calibration();
MainWindow(){
Vars = new Model(this);
Positions = new Positions(this);
CalibrationWindow = new Calibration(this);
}
private void OpenButton_Click(object sender, RoutedEventArgs e)
{
PositionsWindow.Show();
}
private void TextBoxUpdate_Click(object sender, RoutedEventArgs e)
{
Vars.TestVar = TestBox.Text;
}
}
Edit: (to complete the answer to the question):
Now, if you want the Windows to change stuff to each other, just create functions in MainWindow that change stuff in each of the Windows. And with MW you can call these functions from any child Window
For me the best is using Subscribe/Publisher event-based way, here is the way to do it. (i recreate the code so that you can understand)
1) add an event publisher in your child windows.
public partial class ChildWindows : Window
{
// the is the event publisher
public event Action<string> ChildUpdated;
public ChildWindows()
{
InitializeComponent();
}
private void updateParentBtn_Click(object sender, RoutedEventArgs e)
{
// pass the parameter.
ChildUpdated(updateTb.Text);
}
}
2) Subscribe the publisher in your parent windows.
public partial class MainWindow : Window
{
Model Vars;
ChildWindows childWindows;
public MainWindow()
{
InitializeComponent();
Vars = new Model();
childWindows = new ChildWindows();
//this is the event subscriber.
childWindows.ChildUpdated += ChildWindows_ChildUpdated;
}
//do whatever you want here.
void ChildWindows_ChildUpdated(string obj)
{
// Update your Vars and parent
Vars.TestVar = obj;
updateLbl.Content = Vars.TestVar;
}
private void openButton_Click(object sender, RoutedEventArgs e)
{
childWindows.Show();
}
private void textBoxUpdate_Click(object sender, RoutedEventArgs e)
{
}
}
3) In this case, when i type inside the textbox in my child windows, and press a button, it will appear on a label inside my parent windows.
P/S : i had changed the ParentUpdated to ChildUpdated. thanks to #Adrian for constructive feedback
example
first of all, I normally write code using MVVM or MVVMC, but for a very simple project, I want to try doing everything the "old way", meaning writing a simple to understand app using only code behind logic, and no INotifyPropertyChanged interface.
For that purpose, I have created a very simple Employee sample app with a class that loads a list of Employees to an Obersvablecolletion. The problem ist: after I set the Itemssource and DataContext, after Loading, my DataGrid does not get updated. Of course I could set the DataContext again after loading, but is there a better way to do so? Some kind of telling the DataGrid in code behind that its contents have changed and Invaldiate them?
Here is my sample code:
public partial class MainWindow : Window
{
private EmployeeList _MyList;
public MainWindow()
{
InitializeComponent();
_MyList= new EmployeeList();
_MyList.Employees = new ObservableCollection<Employee>();
_MyGrid.DataContext = _MyList;
_MyGrid.ItemsSource = _MyList.Employees;
}
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void _AddButton_Click(object sender, RoutedEventArgs e)
{
AddWindow newWindow = new AddWindow();
if (newWindow.ShowDialog() == true)
{
_MyList.Employees.Add(newWindow.NewEmployee);
}
}
private void _LoadButton_Click(object sender, RoutedEventArgs e)
{
_MyList.Load();
//Creates new_MyList.Employees and fills with content from a file. After that, my DataGrid does not get updated
}
Do not create a new _MyList.Employees collection instance.
Instead clear and re-fill the existing one:
_MyList.Employees.Clear();
for (var employee in employeesFromFile)
{
_MyList.Employees.Add(employee);
}
Since you aren't using a Binding for _MyGrid.ItemsSource, it's also not necessary to set _MyGrid.DataContext.
If you are using ObservableCollection, then DataGrid will update itself without any extra issues.
But if you are using List<Employee>, then you need to re-assign the ItemsSource like below :
private void _AddButton_Click(object sender, RoutedEventArgs e)
{
AddWindow newWindow = new AddWindow();
if (newWindow.ShowDialog() == true)
{
_MyList.Employees.Add(newWindow.NewEmployee);
// re-assign the `ItemsSource`
_MyGrid.ItemsSource = null;
_MyGrid.ItemsSource = _MyList.Employees;
}
}
You can remove the existing item-source by assigning null value to the data-grid and then assign updated item-source like,
_MyGrid.ItemsSource = null;
_MyGrid.ItemsSource = _MyList.Employees;
I added an event handler to my code and it broke all access to the CollectionViewSources in the SystemHTA class saying "The calling thread cannot access this object because a different thread owns it". My class was working when "this.systemHTA = new SystemHTA();" was placed outside of the DeviceManager_StateChanged() function.
public partial class MainWindow : Window
{
private DeviceManager DeviceManager = DeviceManager.Instance;
public SystemHTA systemHTA;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DeviceManager.StateChanged += new EventHandler<DeviceManagerStateChangedEventArgs>(DeviceManager_StateChanged);
DeviceManager.Initialize();
}
void DeviceManager_StateChanged(object sender, DeviceManagerStateChangedEventArgs e)
{
if (e.State == DeviceManagerState.Operational)
{
this.systemHTA = new SystemHTA();
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.systemHTA.GetViewSourceTest();
}
}
public class SystemHTA
{
private CollectionViewSource _deviceTestSource;
public SystemHTA()
{
_deviceTestSource = new CollectionViewSource();
_deviceTestSource.Source = CreateLoadData<HWController>.ControllerCollection;
}
public void GetViewSourceTest()
{
ListCollectionView view = (ListCollectionView)_deviceTestSource.View; //This creates an error saying a thread already owns _deviceTestSource
}
}
Ok, CollectionViewSource derived classes, BindableList, ObservableCollection etc these classes can only be created in main dispatcher thread only.
However you have to try something of following sort,
Create your collectionviewsource only in your WPF derived classes, use List<> classes to load your objects in different thread and once done, you can transfer from list to collectionviewsource as follow, I would recommend BindingList because you can add multiple items disabling the refresh to remove flickering.
Create your collection object implicitly in your WPF classes as follow
public class MyWindow : UserControl{
BindingList<MyObject> ObjectList = new BindingList<MyObject>;
public MyWindow(){
ObjectList.AllowAdd = true;
ObjectList.AllowDelete = true;
ObjectList.AllowEdit = true;
}
public void LoadObjects(){
ThreadPool.QueryUserItem( (s)=>{
// load your objects in list first in different thread
List<MyObject> list = MyLongMethodToLoadObjects();
Dispatcher.BeginInvoke( (Action)delegate(){
list.RaiseEvents = false;
foreach(MyObject obj in list){
ObjectList.Add(obj);
}
list.RaiseEvents = true;
list.ResetBindings();
});
});
}
}
I dont know this code does not format correctly but you may try seeing it in visual studio to get correct idea.
i got a ListView in the windows form.When form loads ListView loading with personel objects. I want to do when some user double clicks on ListView , gets the personel object from the ListView.SelectedItem property and open a new form and transfer this object to newly opened form.
here is my codes for loading personel objects to ListView:
public static void GetAll(ListView list)
{
list.Items.Clear();
using (FirebirdEntityz context = new FirebirdEntityz())
{
ObjectQuery<PERSONEL> query = context.PERSONEL;
foreach (var item in query)
{
var mylist = new ListViewItem { Text = item.NAME };
mylist.SubItems.Add(item.SURNAME);
mylist.Tag = item;
list.Items.Add(mylist);
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
GetAll(listView1);
}
This is my personel object for transfer:
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
PERSONEL personel = (PERSONEL)listView1.SelectedItems[0].Tag;
}
You could probably just add a public PERSONEL property to the form, which you would then set in your SelectedIndexChanged event handler. Then any code that has access to your selector form could access your custom selected PERSONEL property.
In the new form that will be opened, add a new property in the form class;
private PERSONNEL Personnel{get; set;}
public ShowPersonnel(PERSONNEL _personnel){
this.Personnel = _personnel;
//do whatever you want here
}
In the main form;
private void listView1_SelectedIndexChanged(object sender, EventArgs e){
PERSONNEL personnel = listView1.SelectedItems[0].Tag as PERSONNEL;
Form2 form2 = new Form2();
form2.ShowPersonnel(personnel);
form2.Show();
}
May include typos. Change PERSONNEL to PERSONEL.
One way is to have a public propery
as Factor Mystic has suggested.
Or you could have a parametrized ctor
and pass Personnel object. Although,
this might create some problem with
the design view of the form in Visual
Studio.
You should be able to set the display member of the listview control. Berfore you enter the for loop do something like:
list.DisplayMember = "Name"
Then bind the object.
list.DataSource = query.ToList()
The selected item will give you the object you've binded...
MessageBox.Show(((PERSONEL)list.SelectedItem).Name);
This was how it worked in .net 2.0. But I am sure there is probably a way to do this in 3.0 and greater...