Still learning MVVM and multi-threading and have come across something I cannot figure out.
Users access a list of applicants via a Datagrid for their specific branch location. They can click a button within the datagrid to obtain SelectedItem to open a more detailed view of that application.
The initial time they open this view, the data is correct; however, any further time(s) they open the view the original data is still shown. It does not show the second applicants information in the View unless the application is closed and opened again.
I have read that the ViewModel is stored in memory and I thought, maybe threading would provide a solution. I used the following code to attempt just that; however, no luck.
Can someone please point me in the correct direction on how I can handle this situation, or if I am approaching this totally wrong. :)
Thank you for your time.
private void NewWindowHandler(object sender, RoutedEventArgs e)
{
try
{
Craig_Tools.Data.Globals.selectedAppID = Convert.ToInt32(appIDTextBox.Text);
Thread newWindowThread = new Thread(delegate()
{
Window tempWindow = new Window
{
Title = "Display Applicant",
Content = new Craig_Tools.ApplicationTrackingSystem.userControls.atDisplay(),
SizeToContent = SizeToContent.WidthAndHeight,
//ResizeMode = ResizeMode.NoResize
};
tempWindow.Show();
System.Windows.Threading.Dispatcher.Run();
});
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Error opening Applicant Display");
}
}
I have also tried the following.
private void NewWindowHandler(object sender, RoutedEventArgs e)
{
try
{
Craig_Tools.Data.Globals.selectedAppID = Convert.ToInt32(appIDTextBox.Text);
//create and show window
atDisplay newView = new atDisplay();
ViewModels.displayViewModel viewModel = new ViewModels.displayViewModel();
newView.DataContext = viewModel;
newView.Title = "Applicant ID: " + Craig_Tools.Data.Globals.selectedAppID;
newView.Show();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Error opening Applicant Display");
}
}
What I have figured out using Visual Studio debugger is that the ViewModel is never executed when attempting to open the information for the second applicant. It seems to simply just grab the data that is in memory.
Related
This is my primary way for displaying help topics from within my WinForm button click handlers:
Handler:
private void buttonHelp_Click(object sender, EventArgs e)
{
CutTools.DisplayHelpTopic(this, "create-new-viewport.htm");
}
Base method:
public static void DisplayHelpTopic(Control parent, string topic)
{
try
{
// Use an empty form as the parent so that the help file will not block the CAD software
Form mHelpParent = new Form();
// Use location of this DLL file
System.Reflection.Module mod = parent.GetType().Module;
string path = Path.GetDirectoryName(mod.FullyQualifiedName);
Help.ShowHelp(mHelpParent,
Path.Combine(path, "cut-tools-help.chm"), HelpNavigator.Topic, topic);
}
catch (System.Exception ex)
{
_AcAp.Application.ShowAlertDialog(
string.Format("\nError: {0}\nStackTrace: {1}", ex.Message, ex.StackTrace));
}
}
The forms are displaid inside AutoCAD, BricsCAD or ZWCAD. The about is fine and great. But if I want to simply display the CHM file itself (so no actual form is available) I have to do this:
[CommandMethod("TS_DisplayHelp")]
public void TS_DisplayHelp()
{
// Use location of this DLL file
System.Reflection.Module mod = GetType().Module;
System.Diagnostics.Process.Start(
Path.Combine(Path.GetDirectoryName(mod.FullyQualifiedName), "cut-tools-help.chm"));
}
It works but has one drawback. It spawns a new instance of the help and does not use the same instance.
For example:
You start one of the other commands and show the help via button click. You cancel.
You start a different command and show the help via button click. Help.ShowHelp uses same instance.
You can command and start help via TS_DISPLAYHELP and it starts new instance.
Given the context of TS_DISPLAYHELP I can't work out how to directly use Help.ShowHelp as I can in my button click handlers.
At the moment I have managed to get around this issue by duplicating the DisplayHelpTopic code directly in the command TS_DISPLAYHELP method:
[CommandMethod("TS_DisplayHelp")]
public void TS_DisplayHelp()
{
try
{
// Use an empty form as the parent so that the help file will not block the CAD software
Form mHelpParent = new Form();
// Use location of this DLL file
System.Reflection.Module mod = GetType().Module;
string path = Path.GetDirectoryName(mod.FullyQualifiedName);
Help.ShowHelp(mHelpParent,
Path.Combine(path, "cut-tools-help.chm"), HelpNavigator.Topic, "command-index.htm");
}
catch (System.Exception ex)
{
_AcAp.Application.ShowAlertDialog(
string.Format("\nError: {0}\nStackTrace: {1}", ex.Message, ex.StackTrace));
}
}
I know that my default topic is "command-index.htm".
I am happy with the above resolution.
I have these pieces of code:
private void btnPlanning_Click(object sender, RoutedEventArgs e)
{
LoadPage("PlanningView.xaml");
}
private void LoadPage(string APage)
{
try
{
frameMainView.Source = new Uri(APage, UriKind.Relative);
}
catch (Exception ex)
{
string errorString = $"Resource <{APage}> not found! ";
DoLogD(errorString + " " + ex.Message);
MessageBox.Show(errorString);
}
}
Clicking on btnPlanning button, LoadPage is called passing a string with the name of the XAML resource I want to load in the frame control frameMainView.
If the given resource doesn't not exist, I would like to catch the exception and inform the user.
The problem is that when i click the button (and the resource doesn't exist), I get in any case
PresentationFramework.pdb not loaded
and a internal System.IO.IOException telling me the resource is not available.
Why my try-catch block is not working?
there are many ways to load the pages into the frame:
By setting the source
frameMainView.Source = new Uri("PlanningView.xaml",UriKind.RelativeOrAbsolute);
By setting the Content:
frameMainView.Content= new PlanningView();
By using the NavigationService:
frameMainView.NavigationService.Navigate(new PlanningView());
It´s a user interface initialization Problem. Can you get more information from the visual Studio "Output" Window?
I have a TreeView that contains database objects that are basically folders. I want to be able to click on a "folder" in the tree and have it populate a set of controls with data about that "folder". While this all works fine with the code I've written, the issue is that using the arrow keys on the keyboard to go up and down the folder list will eventually hang the application. My assumption is that the background worker I am using to populate the controls is getting hung up.
I've searched and I can't find anything similar to my issue.
Here's my tree view afterselect code.
private void dmTree_AfterSelect(object sender, TreeViewEventArgs e)
{
object[] tagParts = e.Node.Tag as object[];
SelectedFolderNumber = tagParts[1].ToString();
if (!String.IsNullOrEmpty(SelectedFolderNumber) && SelectedFolderNumber != "0")
{
//update mini profile
if (bgwMiniProfile.IsBusy)
{
bgwMiniProfile.CancelAsync();
}
while (bgwMiniProfile.CancellationPending)
{
Application.DoEvents();
}
bgwMiniProfile.RunWorkerAsync();
while (bgwMiniProfile.IsBusy)
{
Application.DoEvents();
}
securityPanel.DisplayTrusteeList(folderTrustees);
}
}
securityPanel is a user control on the form.
Here is the DisplayTrusteeList code
public void DisplayTrusteeList(List<DocumentTrustee> documentTrustees)
{
try
{
dgvTrustees.Rows.Clear();
foreach (DocumentTrustee dt in documentTrustees)
{
dgvTrustees.Rows.Add(imagePG.Images[(int)dt.TrusteeType], dt.GetFullName(dmLogin), dt.AccessRights);
}
foreach (DataGridViewRow myRow in dgvTrustees.Rows)
{
ValidateRights(int.Parse(myRow.Cells["dmRights"].Value.ToString()), myRow);
}
dgvTrustees.ClearSelection();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "DisplayTrusteeList");
}
}
And here is the background worker:
private void bgwMiniProfile_DoWork(object sender, DoWorkEventArgs e)
{
if (!bgwMiniProfile.CancellationPending)
{
SetText(txtDocNumber, SelectedFolderNumber);
SetText(txtDocName, Utility.GetProfileValue(adminLogin, SelectedFolderNumber, "DOCNAME"));
SetText(txtClientId, Utility.GetProfileValue(adminLogin, SelectedFolderNumber, "CLIENT_ID"));
SetText(txtClientName, Utility.SetDescription(adminLogin, "CLIENT", txtClientId.Text));
SetText(txtMatterId, Utility.GetProfileValue(adminLogin, SelectedFolderNumber, "MATTER_ID"));
SetText(txtMatterName, Utility.SetDescription(adminLogin, "CLIENT", txtClientId.Text, txtMatterId.Text));
folderTrustees = Utility.GetFolderTrustees(adminLogin, SelectedFolderNumber);
}
else
{
e.Cancel = true;
}
}
I would like to be able to cursor through the tree nodes with the arrow keys and not have the after select code fire until the user lands on a node and stays there for a few seconds. Is that possible?
Thanks and this is my first question. Sorry if the format ins't great. I've used a lot of solutions from here.
I found a better way. Instead of using AfterSelect I'm using NodeMouseClick. This mirrors Windows Explorer functionality. Now the user can cursor up and down the folder tree without any issues. The data to the right will fill in only when they click on the node. This works for me perfectly.
i have this WPF application in which i am trying to make popup window. well window is created and working fine, but what i want to do. that if i press OK/Update button in that popup, The selected values should be passed the the parent window and that popup should be closed.
i have seen this problem solution here..
C# - Return variable from child window to parent window in WPF
But i do not understand how this delegates works..
I have done it like this..
When click on button this method will opens the popup window.
private void btnAddBeneficiaryPopup_Click(object sender, RoutedEventArgs e)
{
try
{
AddBeneficiaryPopup addBen = new AddBeneficiaryPopup(refCustId);
addBen.selectedBeneID += value => selectedBeneficiaryID = value;
addBen.Show();
}
catch (Exception ex)
{ this.MyErrorMessage(ex); }
}
In Popup window in the constructor i have code like this.
public AddBeneficiaryPopup(int id)
{
InitializeComponent();
refCustId = id;
this.LoadReferenceBeneficiary();
}
Now this below Method i am working on and want to change it mostly..
private void cmbRefBene_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string beneId = null;
if (cmbRefBene.SelectedIndex >= 0)
{
try
{
beneId = ((DataRowView)cmbRefBene.SelectedItem).Row.ItemArray[0].ToString();
selectedBeneID = beneId;
bene.OpenConnection(str);
SqlDataReader reader = bene.LookUpSingleBene(int.Parse(beneId));
if (reader.Read())
{
tbName.Text = reader["Name"].ToString();
tbContactNo.Text = reader["ContactNo"].ToString();
btnUpdate.IsEnabled = true;
}
reader.Close();
bene.CloseConnnection();
}
catch (Exception ex)
{
MyErrorMessage(ex);
}
finally
{
bene.CloseConnnection();
}
}
}
As you can see in above code selectedBeneID = beneId; this beneId gives error. as i am trying to assign it selectedBeneID, as i think its a delegate to there must be another way to assigning values to it and passing it to the Parent Window..
But really am not sure how to work with this delegate and what to write to assign value to it.
i am getting error
"cannot implicitly convert type string to "System.Action<string>"
Solution A (getting your one working)
To get your solution running, change the following line in your cmbRefBene_SelectionChanged function:
selectedBeneID = beneId;
to
selectedBeneID(beneId);
Now you should not get the error message and the value should be set correctly.
Solution B
The following solution isn'n the most elegant but it always works:
Give your Popup Window a public Property (selectedBeneID).
public partial class AddBeneficiaryPopup : Window {
public string selectedBeneID;
.....
}
}
Set this property in your cmbRefBene_SelectionChanged function.
MainWindow:
change addBen.Show(); in your Main Window
to
addBen.ShowDialog();
idreturned = addBen.selectedBeneID;
Now The program will wait until you close the Popup.
After that you can access the property of your popup Window and read it out.
I've written an Outlook add-in (OL2010). It has a menu on the ribbon bar with various icons that open new windows that do stuff (hope that's not too in-depth ;)). An example of one of the icon Click handlers is below.
public void OnViewMyTracksClick(Office.IRibbonControl control)
{
try {
MyTracksViewModel viewModel = new MyTracksViewModel();
MyTracksView view = new MyTracksView();
view.DataContext = viewModel;
view.Show();
}
catch (Exception ex)
{
Log.Error("xxxxx", "Error on button click: " + ex.Message + Environment.NewLine + ex.InnerException);
}
}
In Outlook, if I click the button to open this view, I see the memory usage of Outlook.exe increase by 10mb (the window and it's accompanying data). When I close the view, none of that memory is reclaimed. If I click the button again, another 10mb is added, and again, none is released when I close the view.
I thought that this is because I'm creating a new viewmodel everytime, so I added some code to check if it was already instantiated (the view and viewmodel are now registered at the class level, rather that within the method, so that I don't create a new one each time) - _allTracksVM is an instantiation of AllTracksViewModel. _allTracksV is the view.
public void OnViewAllTracksClick(Office.IRibbonControl control)
{
try {
if (_allTracksVM == null)
{
_allTracksVM = new AllTracksViewModel();
}
_allTracksV = new AllTracksView();
_allTracksV.DataContext = _allTracksVM;
_allTracksV.Show();
}
catch (Exception ex)
{
Log.Error("xxxxx", "Error on button click: " + ex.Message + Environment.NewLine + ex.InnerException);
}
}
This didn't seem to make any difference. I then added an EventHandler that would fire when the view was closed:
_allTracksV.Closing += new System.ComponentModel.CancelEventHandler(this.view_RequestClose);
And this set both the objects to null (you can probably tell i'm grabbing at straws at this point):
void view_RequestClose(object sender, EventArgs e)
{
_allTracksVM = null;
_allTracksV = null;
}
The memory remains allocated. How can I dispose of the objects correctly (or perhaps I should be instantiating them differently), so that they don't consume another chunk of memory each time they are opened?
Thanks
Mick
Checking the VM for Null was a good idea but by setting it to null in the close handler, it is rendered useless :)
You could try to make the view a field in the containing class rather than a local variable. That way you don't need to create a new view each time.
As for the memory usage, it seems to me that you're doing it right. Since the GC only collects when necessary, it will take some time until the memoryusage declines.