In Lightswitch by default when you want to delete an item from a on screen List or DataGrid, you can click the delete button provided by default, or you can programmatically delete the item from the VisualCollection by calling in the 'screen code'
this.VisualCollection<Entity>.SelectedItem.Delete()
or
this.VisualCollection<Entity>.DeleteSelcted()
However this marks the selected row/entity for deletion and places an 'X' in the leftmost column of the DataGrid/List. The row remains visible to the user, and while this does reflect the transactional/asynchronous nature of the process, it is confusing to users who expect the row to be removed from the list. For example:
Customer: I deleted it why is it still there...
Me: Did you notice the x to the left?
Customer: Oh.... um...
Me: Yeah... you need to click save for the changes to be persisted to the database.
Customer: ....I'll pretend like that makes sense.
Me: .... that's a good lad ....
A better way would be to remove the item from the VisualCollection when delete is called then silently persist the change. Not having the annoying waiting/loading popup because of the asynchronous nature.
I have tried calling this.VisualCollection<Entity>.RemoveSelected() but that results in a LightSwitchException - Current item cannot be removed
I have tried saving the record after I call Delete() but that saves all changes on screen, and also displays the aforementioned popup and is not a good user experience.
After I make any changes to a DataGrid programatically, I call this function I wrote. It will check for any validation errors and inform the user if they exist so that they can be corrected. Otherwise, it will silently persist the changes in the background. I'm not sure what you mean by "waiting/loading popup". The only indication is the small blue spinner next to the screen name on the tab for a second or two.
private void ValidateAndSave()
{
//Check for validation errors
if ((this.Details.ValidationResults.HasErrors == false)) {
//Save the changes to the database
try {
this.DataWorkspace.DatabaseNameData.SaveChanges();
} catch (Exception ex) {
this.ShowMessageBox(ex.ToString());
}
} else {
//If validation errors exist,
string res = "";
//Add each one to a string,
foreach (object msg_loopVariable in this.Details.ValidationResults) {
msg = msg_loopVariable;
res = res + msg.Property.DisplayName + ": " + msg.Message + "\r\n";
}
//And display them in a message box
this.ShowMessageBox(res, "Validation error", MessageBoxOption.Ok);
}
}
Note: I converted this from VB.NET so it's probably not a drop in replacement. In particular I think the Message Box is done differently so double check that.
Related
I have inherited a Windows Forms Application. We are having some performance issues, when trying to save a new record to the SQL Table. It hangs.
For the most part, I have ruled out the database or Table Structure as the problem. I don't believe that is the case here.
I am very new to this, and am having trouble stepping through and finding the root of the problem.
The form basics: (Only included what I thought was relevant code, but can add more if needed)
public partial class frmBadgeCreate : daBaseForm
{
private boBadger_History oBadger_History;
Form mainFormHandler;
private string whome;
}
public frmBadgeCreate(String cutomItem)
{
InitializeComponent();
if (daAppDesktop.IsRunning)
{
oBadger_History = new boBadger_History();
oBadger_History.GetAll(); /// This line right here seems to have some importance
whome = customItem;
}
}
public void saveitall() /// Save Action that hangs
{
// listing of form variables to be saved to table columns
var vlast = textbox_H_lname.Text;
var vfirst = textbox_H_fname.Text;
. . .and on and on . . .
var badger_History = new Badger_History() {hlastname = vlast, vfirstname = vfirst . . . and on and on . . };
oBadger_History.Add(badger_History);
oBadger_History.Save(); /// This is where things just hang forever.
Because this is a 'Lone Ranger App' that was handed to me, I am struggling to grasp it. What really confuses me is that when I comment out the 'oBadger_History.GetAll()' line, the save works very fast! Instantly. When I add the line back in, it hangs. I only know this, because I have spent days commenting out each line, one by one, and testing results.
The oBadger_History.GetAll(); looks like it is somehow used for the auto complete feature, so it is needed.
What has me totally scratching my head is that I can't see the connection between the 'GetAll' and the save. Why would the getAll impact the save function at all?
Here is the GetAll code, if that sheds any light:
public daBindingList<Badger_History> GetAll()
{
BadgerContext cn = (BadgerContext)this.context;
List<Badger_History> ents = cn.Badger_History.ToList();
this.EntityList = new daBindingList<Badger_History>(ents);
return this.EntityList;
}
Again, I don't believe that the SQL database/tables are the problem, seeing that I can get the save to work properly by removing the one line of code. I just can't seem to find a way to resolve
This line:
List<Badger_History> ents = cn.Badger_History.ToList();
Will load EVERY row in the Badger_History table into memory. How many records are in this table?
If there are lots of rows, then when you call Save(); (which I assume is some sort of wrapper around SaveChanges(), then EF will look through every row for anything that has changed. In your case, there may be 0 rows changed, as all you are interested in, is the row you are adding.
To speed things, you could change loading the data into a 'non-tracking' query
List<Badger_History> ents = cn.Badger_History.AsNoTracking().ToList();
This will still load in all the records, but they will no longer be counted when trying to save.
Using an application level add-in, I'm performing some operations on document open that require revisions (tracked changes) to be rendered inline and not hidden, so that they are contained within the Range of the document. After consulting the documentation, I thought that all I had to do was change the the view properties of the active window: MarkupMode claims to do what I want.
But this property seems to be completely disconnected from how revisions display in the document! To test this, I tried toggling the mode manually in a document, and looking at MarkupMode, and checking it immediately afterwards in an onSelectionChange event handler. I went ahead and tracked a bunch of properties of ActiveWindow.View, for good measure. To my surprise and chagrin, when I looked at the locals with changes rendered inline:
... and compared the values to those with changes hidden:
Nothing changed! What gives? Am I not looking at the right property/properties to ensure that changes are rendered inline? Is Microsoft completely incapable of writing meaningful documentation? I'll point out that I tried to make the property change in code as well, to see if the rendering of revisions would change, with no success. I would appreciate any feedback.
Edit: Simple code to duplicate issue:
private void ThisAddIn_Startup(object sender, EventArgs e)
{
Application.WindowSelectionChange += application_WindowSelectionChange;
}
private void application_WindowSelectionChange(Selection sel)
{
var testDoc = sel.Document;
var test = new
{
testDoc.ActiveWindow.View,
testDoc.ActiveWindow.View.ShowRevisionsAndComments,
testDoc.ActiveWindow.View.ShowInsertionsAndDeletions,
testDoc.ActiveWindow.View.MarkupMode,
testDoc.ActiveWindow.View.RevisionsMode
};
}
Edit 2: Beyond this contrived example, I need to have control over the markup style of Revisions because I am searching for text on DocumentOpen that may include text that is present as Revision objects. More specifically, I am attempting to do the following, using the above text (with text "powerful way to help you prove " deleted in a revision):
private void ThisAddIn_Startup(object sender, EventArgs e)
{
Application.DocumentOpen += application_DocumentOpen;
}
private void application_DocumentOpen(Document doc)
{
// expected text, as taken from screengrab example above. Includes
// text removed in a revision
string expectedText = "Video provides a powerful way to help you prove your point.";
// make sure that we're in print view
if (doc.ActiveWindow.View.Type != WdViewType.wdPrintView)
{
doc.ActiveWindow.View.Type = WdViewType.wdPrintView;
}
// attempt to ensure that document revisions are marked up inline. Does not accomplish anything
doc.ActiveWindow.View.MarkupMode = WdRevisionsMode.wdInLineRevisions;
// attempt to locate text. Will fail if revisions are not marked up inline (deletion is not part of document content range otherwise)
var locatedRange = doc.Content.OccurrenceOfText(expectedText);
}
// extension method to locate text inside a range. Searching entire Content in this example
private static Range OccurrenceOfText(this Range rng, string text)
{
rng.Find.Forward = true;
rng.Find.Format = false;
rng.Find.Execute(text);
if (!rng.Find.Found)
{
throw new Exception("Unable to locate text! Are Revisions marked up inline?");
}
// return brand new range containing located content
return rng.Document.Range(rng.Start, rng.End);
}
Edit 3: As Cindy made clear, my problem was that I was using the wrong property: I needed to use the View.RevisionsFilter.Markup property to make the change. Additionally, an issue that I hadn't diagnosed is that depending on the View properties, it is entirely possible that the Range returned from a search performed as I do returns a Text property that is different than the text that was searched with. This can happen if Revision objects are present inside the Range.
It works for me. What I did:
Created a test document. Used =rand() ->Press Enter to put some text in the document. Copied in the phrase, changing "powerful" to "useful".
Turned on track changes and made some changes, including selecting "useful" and typing "powerful".
Made sure the changes were displaying in balloons and the "Original" view of the mark-up was being shown.
Saved and closed the document.
Put a break-point on the line that calls OccurrenceOfText, then F5 to start the add-in in debug mode.
Opened the saved document. The Open event fired. I checked the values of the View.Markup and View.RevisionsView - they had changed to inline and "Final". Stepped through the rest of the code and it executed correctly (found the occurrence).
I needed to modify your code slightly since I don't have your "wrapper". I also changed the way OccurrenceOfText returns the Range: no need to do it in the complicated manner you use...
I note that you do not appear to set View.RevisionsView - perhaps this is why you're not seeing the result you expect?
private void Application_DocumentOpen(Microsoft.Office.Interop.Word.Document doc)
{
//Test revisions
// expected text, as taken from screengrab example above. Includes
// text removed in a revision
string expectedText = "Video provides a powerful way to help you prove your point.";
// make sure that we're in print view
if (doc.ActiveWindow.View.Type != Word.WdViewType.wdPrintView)
{
doc.ActiveWindow.View.Type = Word.WdViewType.wdPrintView;
}
// attempt to ensure that document revisions are marked up inline. Does not accomplish anything
Word.View vw = doc.ActiveWindow.View;
vw.MarkupMode = Word.WdRevisionsMode.wdInLineRevisions;
vw.RevisionsView = Word.WdRevisionsView.wdRevisionsViewFinal;
// attempt to locate text. Will fail if revisions are not marked up inline (deletion is not part of document content range otherwise)
var locatedRange = OccurrenceOfText(doc.Content, expectedText);
}
// extension method to locate text inside a range. Searching entire Content in this example
private static Word.Range OccurrenceOfText(Word.Range rng, string text)
{
rng.Find.Forward = true;
rng.Find.Format = false;
rng.Find.Execute(text);
if (!rng.Find.Found)
{
throw new Exception("Unable to locate text! Are Revisions marked up inline?");
}
// return brand new range containing located content
return rng; //.Document.Range(rng.Start, rng.End);
}
I am developing a c# windows form application. In my application i have 3 forms (main form that has a list box and two buttons (Check in and check out), check in form and the check out form). On the main form, the list box contain user names, if a user select their name for the first time, the check in button must be enabled for the user to check in... But if the user checks in and then closes the application, when they reopen it, the button check out should be enabled and check in disabled.
I have been told to use the application/user states, but since I'm new in programming, i don't know how to implement the windows form states.
What should i do?
Thank you
There is no such thing as "Windows Forms states". You have several options to implement somthing like this, among which are:
Use a database (this makes sense if you have a varying number of users and a database server available)
Use user settings (this is a builtin mechanism of the .NET framework, but may not be suitable for lots of users)
Use a simple XML file to store the states of all users.
All three solutions require you to sort of "get into things". Write more about what you have available (database server, etc.) or whether you want a fixed number of users and I can extend this answer to help you get started.
I'm going to line out how to do number 2:
Create a little helper class that assigns a state to a user name:
public class UserState
{
public string UserName { get; set; }
public bool CheckedIn { get; set; }
public override string ToString() { return String.Format("{0}={1}", UserName, CheckedIn); }
}
This class allows you to store a user name and the checked in state and by calling ToString() get a value in the form "user=false".
Then, create a user scoped application setting (go to settings-tab of project settings and add a new setting of type System.Collections.Specialized.StringCollection) named UserStates. You can access this setting from code as Properties.Settings.Default.UserStates. It is basically a list of strings.
To add and persist a new entry you could do this:
UserState state = new UserState() { UserName = "Test", CheckedIn = false };
Properties.Settings.Default.UserStates.Add(state.ToString());
Properties.Settings.Default.Save();
The state for user "Test" (and the previously existing entries) are now stored across program restarts.
Now the idea is to build a list of users and their states when starting the program and to store this list when exiting.
Declare this as a member variable in the class:
private List<UserState> userStates = new List<UserState>();
Do the following in the form's OnLoad event:
if (Properties.Settings.Default.UserStates == null || Properties.Settings.Default.UserStates.Count == 0)
{
// Add your users to the collection initially. This is the first
// run of the application
userStates.Add(new UserState() { ... });
...
}
else
{
// Each line in the setting represents one user in the form name=state.
// We split each line into the parts and add them to the internal list.
for (int i = 0; i < Properties.Settings.Default.UserStates.Count; i++)
{
string stateLine = Properties.Settings.Default.UserStates[i];
string[] parts = stateLine.Split('=');
userStates.Add(new UserState() { UserName = parts[0].Trim(), CheckedIn = Boolean.Parse(parts[1].Trim()) });
}
}
This creates a new entry in an internal list of users for each stored line in the collection setting.
When a button is clicked, change the state in the respective UserState object in the list.
Do the following in the form's OnClose event:
// Create the collection from scratch
Properties.Settings.Default.UserStates = new System.Collections.Specialized.StringCollection();
// Add all the users and states from our internal list
foreach (UserState state in userStates)
{
Properties.Settings.Default.UserStates.Add(state.ToString());
}
// Save the settings for next start
Properties.Settings.Default.Save();
This persists the current list of user states to the setting.
Please note: I have tested this in Visual Studio now and it works. I leave the question of how to map the list box entries to the UserState objects in the internal list to you/as topic for a new question :-D
The downside of this approach: It is not very flexible - adding more states per user involves some coding.
It could be better for you to read about typed datasets and how to store/read them from XML. This gives you some sort of "database feeling" without actually having to use a database.
I am currently using InvalidPluginExecutionException to send the message to the user, but it turns out that the message is in English "Business Process Error" beyond which the error box appears the button "download log file". This is not an error because the user is trying to duplicate a record, as can be seen in the code. Is there other way without having to use InvalidPluginExecutionException to show an alert?
QueryExpression query1 = new QueryExpression();
query1.ColumnSet = new ColumnSet(true);
query1.EntityName = "new_appraisers";
EntityCollection ec = service.RetrieveMultiple(query1);
if (ec.Entities.Count <= 0)
{
log.Tb_Log_Create("Appraiser created");
}
else
{
foreach (Entity app in ec.Entities)
{
if (app["fcg_appraiser"].ToString() == name)
{
log.Tb_Log_Create("appraiser allready exist");
throw new InvalidPluginExecutionException("The name allready exists");
}
if (app["new_login"].ToString() == login)
{
log.Tb_Log_Create("appraiser allready exist");
throw new InvalidPluginExecutionException("The login allready exists.");
}
}
}
The only method to display a message box to the user from a plugin is using an exception from the validation stage. You could use javascript however, perform a simple OData query on the On_Save event of the form, and display an alert box with whatever information you'd like, and cancel the saving of the form.
This would allow you to display whatever custom message you'd like, and keep the plugin from firing and displaying the download file dialog.
I may be little late, however, in more recent versions of CRM there are several possibilites to achieve what you want. The better ones beeing:
Business Rules
Validation using JS and notifying the user using
Form Notifications or
Control Notifications
I hope Microsoft doesn't read this but...
You can also use a synchronous Plugin and be happy with the Business Process Error Dialog popping off. I just found out that this Dialog is hackable to some degree. Just return HTML in the Exeptions Message like so:
throw new InvalidPluginExecutionException(
#"<img height='16px' src='http://emojione.com/wp-content/uploads/assets/emojis/1f644.svg'> <strong>Oh snap!</strong>
It seems the record can not be saved in its current state.
");
Which results in sth. like this:
I'm working on an Outlook Add-In that can work in one of two ways, depending on a user's choice - it can process selected emails, or alternatively, process all emails in the selected folder. I've gotten the first part working, but the second part is giving me trouble, possibly because I'm just adapting the code from the first part incorrectly. I believe the trouble comes down to grabbing the currently selected folder properly in a C# Outlook add-in. I'm using .NET 3.5 and Outlook 2007, by the way.
First, the email code - if a user selects one or more emails in their inbox, and runs my add-in with the "selected emails" option, the following code is run (and works fine!):
public static void processSelectedEmails(Outlook.Explorer explorer)
{
//Run through every selected email
for (int i = 1; i <= explorer.Selection.Count; i++)
//alternatively, foreach (Object selectedObject in explorer.Selection)
{
Object selectedObject = explorer.Selection[i];
if (!(selectedObject is Outlook.Folder))
{
string errorMessage = "At least one of the items you have selected is not an email.";
//Code for displaying the error
return;
}
else
Outlook.MailItem email = (selectedObject as Outlook.MailItem);
//Do something with current email
}
}
I've tried to adapt this code to do something else if a user goes to the Navigation Pane (on the left by default) in Outlook, selects a folder or subfolder (perhaps Inbox, Sent Items, or another folder they've created). The user can then choose the "process selected folder" option in my Add-In, which will do essentially the same thing as the code above, but process all of the email inside the selected folder. I have set it to only work if the user has selected a single folder.
public static void processFolder(Outlook.Explorer explorer)
{
//Assuming they have selected only one item
if (explorer.Selection.Count == 1)
{
//Make sure that that selected item is a folder
Object selectedObject = explorer.Selection[1];
if (!(selectedObject is Outlook.Folder))
{
string errorMessage = "The item you have selected is not a folder.";
//Code for displaying the error
return;
}
//Code for running through every email in that folder
}
}
I have not yet written the code to actually run through all of the emails in the selected folder, because my code never gets past the if (!(selectedObject is Outlook.Folder)). Even if the most recently selected item is your Inbox, I receive the error I have programmed in at that point. Perhaps I am misusing the explorer.Selection thing? Any help would be much appreciated.
This may be important to answering my question - the add-in has a field called 'explorer', which is generated on startup: explorer = this.Application.ActiveExplorer. This is the 'explorer' that is passed to my functions so that they can know what is selected. As I said, this works fine for selected emails, but does not work for selected folders. Any insight would be greatly appreciated!
Edit 1: It appears that this question is basically a duplicate of Get all mails in outlook from a specific folder, but it has no answers.
Edit 2: I've been doing further research, it appears that I can get virtually the same functionality (but with an additional step unfortunately) by creating a popup to select a folder using the Application.Session.PickFolder() method. Is there any way to do it based on the currently selected folder, instead of forcing the user to pick a new folder?
Edit 3: I have modified the code found here: http://msdn.microsoft.com/en-us/library/ms268994(v=vs.80).aspx to further show what is not working properly for me:
public static void processFolder(Outlook.Explorer explorer)
{
string message;
if (explorer.Selection.Count > 0)
{
Object selObject = explorer.Selection[1];
if (selObject is Outlook.MailItem)
{
message = "The item is an e-mail";
}
else if (selObject is Outlook.Folder)
{
message = "The item is a folder";
}
else
{
message = "No idea what the item is!";
}
Console.WriteLine(Message);
return;
}
}
Whether I select a message, or go to the Navigation Pane and select a folder, I receive the message "This item is an e-mail".
Explorer.Selection is for Items only (MailItem, AppointmentItem, etc.) - not Folders. To get access to the currently selected Folder you would need Explorer.CurrentFolder.
Folder.Items would provide you access to all the Items in a given Folder.