How to Terminate an Application Properly and Save Data - c#

I have recently come across the App.Current.Terminate method, which I would like to use in an application. Before calling this, however, I need to save some data. For some reason, even though the save seems to be shown as effective, once the application is terminated the old settings are restored. I cannot figure out what is going on. I have a ListPicker, and I am trying to change the theme setting in my application between light and dark, using Jeff Wilcox's ThemeManager.
SettingsPage.xaml
<Grid.Resources>
<DataTemplate x:Name="PickerItemTemplate">
<StackPanel Tap="stk_Tap">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
...
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme"
ItemTemplate="{StaticResource PickerItemTemplate}"/>
SettingsPage.xaml.cs
List<Theme> themeList;
private int currentThemeIndex;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
themeList = new List<Theme>();
themeList.Add(new Theme() { Name = "light", name = "light" }); //Name subject to change
themeList.Add(new Theme() { Name = "dark", name = "dark" }); //Name subject to change
ThemeListPicker.ItemsSource = themeList;
//make sure ThemeListPicker shows current theme when NavigatedTo
if (Settings.LightTheme.Value)
{
ThemeListPicker.SelectedIndex = 0;
currentThemeIndex = 0;
}
else
{
ThemeListPicker.SelectedIndex = 1;
currentThemeIndex = 1;
}
}
private void stk_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
if (ThemeListPicker.SelectedIndex != -1)
{
//avoid asking the question if the selected item is the same as the current item
//doesn't quite work properly? Skips when the item is changed for the first time
if(ThemeListPicker.SelectedIndex != currentThemeIndex)
{
MessageBoxResult result = MessageBox.Show("The app must be restarted to apply theme changes."", "", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
var selectedItem = (Theme)(sender as StackPanel).DataContext;
switch (selectedItem.name)
{
case "light":
Settings.LightTheme.Value = true;
currentThemeIndex = 0;
break;
case "dark":
Settings.LightTheme.Value = false;
currentThemeIndex = 1;
break;
default:
Settings.LightTheme.Value = true;
currentThemeIndex = 0;
break;
}
Terminate();
}
}
}
private void Terminate()
{
App.Current.Terminate();
}
Theme.cs
public class Theme
{
public string Name
{
get;
set;
}
public string name //name to use in determining current theme
{
get;
set;
}
}
Settings.cs //writes key/value pair to IsoStore
//Theme - set to true by default
public static readonly Setting<bool> LightTheme = new Setting<bool>("LightTheme", true);
App.xaml.cs
public App()
{
...
//Theme Manager
if (Settings.LightTheme.Value)
ThemeManager.ToLightTheme();
else
ThemeManager.ToDarkTheme();
...
}
The main problem is that the theme never changes. I set a breakpoint in App.xaml.cs to check the current value of Settings.LightTheme.Value on application load, which always stays as true which sets the app to the light theme. In SettingsPage.xaml.cs when a user selects either the light or dark option in the ListPicker, I detect that the value of Settings.LightTheme.Value switches between true and false. Immediately after, since the user must select OK on the MessageBox to restart the application, the app is terminated. Upon restarting, Settings.LightTheme.Value is true. I am not sure how to fix this?

Call IsolatedStorageSettings.Save() before Terminate.

In App.xaml.cs the following method can be found. Simply add your 'theme saving' code to this method.
// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
//theme saving code goes here
}

Related

WPF DataGrid SelectedItem strange behaviour

We are seeing some strange behavior surrounding the SelectedItem property in our DataGrid. Some background information:
The DataGrid displays the result of a query to our database.
There is a button that allows the user to manually refresh the results in the DataGrid. There is an auto-refresh mechanism whereby the results will automatically refresh every 30 seconds.
What we are seeing is the SelectedItem property will always become index 0 of the ItemsSource for the Datagrid when the auto-refresh occurs. But we want the currently selected row to remain the selected row after the refresh. However, if the user manually clicks refresh, the selected row remains the same after the refresh which is strange because the same code is running for the refresh logic. And yes, we have code that remembers the currently selected item which then gets set again after the refresh has been completed.
Here is some of the relevant code:
<UserControl.Resources>
<CollectionViewSource Source="{Binding DataGridResults}" x:Key="ReferralItemsSource"/>
</UserControl.Resources>
<customControls:CustomDataGrid x:Name="GridControl"
ItemsSource="{Binding Source={StaticResource ReferralItemsSource}}"
SelectedItem="{Binding DataContext.SelectedReferral, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
IsReadOnly="False"
IsSynchronizedWithCurrentItem="True"
SelectionMode="Single">
private async void RefreshWorklist(bool invokedByAutoRefresh = false)
{
try
{
if (Initialising || ShowSpinner || IsProcessing || ShowRefreshSpinner || IsCurrentWorklistDeleted || !_sessionData.IsActive()) return;
IsProcessing = true;
RefreshWorklistCommand.RaiseCanExecuteChanged();
if (CurrentWorklistId != null)
{
var selectedReferralId = SelectedReferral.pk_Referral_ID;
if (invokedByAutoRefresh)
{
// Refresh has been invoked by _timer, so show spinner on the results page only
ShowRefreshSpinner = true;
}
else
{
// User has manually clicked refresh button so show app wide spinner
ShowSpinner = true;
if (_timer != null)
{
SetupWorklistRefreshTimer(); // Setup _timer again so that it will refresh again at an appropriate time
}
}
Referrals = await _referralRepository.GetReferralsFromWorklistAsync(CurrentWorklistId.Value, invokedByAutoRefresh);
if (Filters.Count > 0)
{
var listOfReferralPks = ReferralFiltering.GetFilteredResults(Referrals, Filters.Where(f => f.HasBeenApplied).ToList());
var filteredResults = Referrals.Where(r => listOfReferralPks.Contains(r.pk_Referral_ID)).ToList();
DataGridResults = MapReferralLookupItemsToReferralLookupItemViewModels(filteredResults);
}
else
{
DataGridResults = MapReferralLookupItemsToReferralLookupItemViewModels(Referrals);
}
SelectedReferral = DataGridResults.FirstOrDefault(r => r.pk_Referral_ID == selectedReferralId);
}
}
catch (Exception e)
{
_errorHandler.DisplayError(e);
}
}
As explained earlier, RefreshWorklist() is called by the manual refresh invoked through a Command:
private void Execute_RefreshWorklist()
{
RefreshWorklist();
}
Or automatically through the use of a Timer:
private void SetupWorklistRefreshTimer()
{
_timer?.Dispose();
var refreshInterval = _userSettingsRepository.GetIntegerSystemSetting("ReferralsWorklistRefreshInterval");
if (refreshInterval <= 0) return; // If this is 0 or below then the refresh should be disabled
if (refreshInterval < 10) // If it is less than 10 then set it to 10 to avoid too many MT calls
{
refreshInterval = 10;
}
var timeUntilFirstTick = refreshInterval * 1000;
_timer = new Timer((s) => RefreshWorklist(true), null, timeUntilFirstTick, refreshInterval * 1000);
}
And finally the SelectedItem property view model binding property:
public ReferralLookupItemViewModel SelectedReferral
{
get { return _selectedReferral; }
set
{
if (_selectedReferral != value)
{
_selectedReferral = value;
OnPropertyChanged();
}
}
}
Does anybody have any idea as to why this behavior is occurring? Is it something to do with the Timer? I appreciate this is not a simple question so please ask away for more information.
You need to assign properties in Binding with the UI on the UI thread.
Replace your Timer with a DispatcherTimer or use Dispatcher.Invoke or Dispatcher.BeginInvoke inside the existing Timer callback when calling RefreshWorklist.
By pressing the Button you are already on the UI thread, but Timer has its own thread that is different from the UI thread.
DispatcherTimer callback are called on the UI thread instead https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatchertimer?view=netframework-4.0

Show additional information in a custom ribbon

Today I came across an idea that could come in handy in my daily work. I wanted to create an outlook add-in that shows the user-id (samAccountName) of the sender. I created a new Outlook Add-In and tried my best (I've never created one before).
This is what it looks like at the moment:
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
_explorer = Application.ActiveExplorer();
_explorer.SelectionChange += _explorer_event;
_userSearcher = new ActiveDirectoryUserSearcher();
}
private void _explorer_event() {
try {
if (Application.ActiveExplorer().Selection.Count > 0) {
var selection = Application.ActiveExplorer().Selection[1];
if (selection is Outlook.MailItem) {
var item = selection as Outlook.MailItem;
var rec = item.Application.Session.CreateRecipient(item.SenderEmailAddress);
var user = rec.AddressEntry.GetExchangeUser();
var mail = user.PrimarySmtpAddress;
var user2 = _userSearcher.First($"(mail={mail})");
//here I'd have to pot some logic to set the text of the control,
//such as "ribbon.EditBox.SetText("blabla");
}
}
}
catch (Exception e) {
Console.WriteLine(e);
throw;
}
}
Now, to show the "content" I'd like to create a new Ribbon element (maybe someone can tell if it's possible to show additional information e.g. below the message body or so?). I've added a new element to my project and called it "DisplayElement".
From different internet sources I tried to figure out how to handly those controls and added the following to the DisplayElement.cs:
public string TextBoxText { get; set; }
public DisplayElement()
{
}
public string onGetText(Office.IRibbonControl control) {
switch (control.Id.ToLower()) {
case "user-id-text":
return TextBoxText;
}
return string.Empty;
}
public void onChange(Office.IRibbonControl control, string value) {
switch (control.Id.ToLower()) {
case "user-id-text":
TextBoxText = value;
break;
}
return;
}
Unfortunately now I'm totally out. I canÄt figure out how to set the text of the editBox to the senders User-Id.
I'd need something like "_ribbon.EditBox.SetText("blabla");"
DisplayElement XAML:
<tabs>
<tab idMso="TabAddIns">
<group id="ContentGroup" label="Content">
<button id="user-id-text" label="Insert Text"
screentip="Text" onAction="OnTextButton"
supertip="Inserts text at the cursor location."/>
<editBox id="labelContent" getText="onGetText" onChange="onChange" enabled="false" screentip="User-ID" supertip="Displays the User-ID of the E-Mail Sender."/>
</group>
</tab>
</tabs>
You can get to your ribbon using Globals.Ribbons.MyRibbonName.
Be sure, that in your MyRibbonName.cs designer's file is
partial class ThisRibbonCollection
{
internal MyRibbonName MyRibbonName
{
// Getter
}
}
Then, in your ThisAddIn file, you can do following:
public partial class ThisAddIn
{
private MyRibbonName myRibb;
private Explorer exp;
private void ThisAddIn_Startup(object sender, EventArgs e)
{
myRibb = Globals.Ribbons.MyRibbonName;
exp = Application.ActiveExplorer();
exp.SelectionChange += exp_SelectionChange;
}
}
This way you can get to all properties and elements in your MyRibbonName Ribbon, including EditBox you was looking for in your Q.
Edit: I created my ribbon in the following manner:
Left mouse click on a project
Select Add > New item
Select Ribbon (Visual Designer)
In MyRibbonName.Designer.cs, partial class ThisRibbonCollection is already present.
All you need to do is add visual elements in your ribbon and implement your application's business logic. This is pretty similar to a Windows Forms developing.

Two ListPicker, one TextBox. Result not displayed. Master-Detail?

I have two ListPickers originating from one TextBox.
private void textBox3_GotFocus(object sender, RoutedEventArgs e)
{
string a = "Domestic";
string b = Convert.ToString(textBox2.Text);
string c = "Foreign";
if (b == a)
{
NavigationService.Navigate(new Uri("/ListPickerExchanges1.xaml", UriKind.Relative));
}
else if (b == c)
{
NavigationService.Navigate(new Uri("/ListPickerExchanges2.xaml", UriKind.Relative));
}
Both ListPickers load and give you the option to choose something from the list. But only one ListPicker will displays the selection back into TextBox3 , and it is always the one under the second Else If (b == c) condition.
The ListPickerExchanges1 under the first condition will not display the selection back into textbox3.
But if i change the code under the second Else If condition to navigate to Exchanges1 instead of Exchanges2, then the Exchanges1 listpicker displays back the selection into textbox 3, and Exchanges2 does not.
Which means, Everything under the second condition works, and does not under the first condition.
Here is the code behind ListpickerExchanges1, which is intended to display the selection back into the textbox.
public partial class ListPickerExchanges1 : PhoneApplicationPage
{
public ListPickerExchanges1()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(ListPickerExchanges1_Loaded);
}
private void ListPickerExchanges1_Loaded(object sender, RoutedEventArgse)
{
ListBoxExchanges.ItemsSource = Page2.ListExchanges;
}
public void Selection(object sender, System.Windows.Input.GestureEventArgs e)
{
ListBox myselecteditem4 = (ListBox)sender;
ItemsModeling4 item4 = (ItemsModeling4)myselecteditem4.SelectedItem;
if (item4 != null)
{
NavigationService.Navigate(new Uri("/Broker.xaml?name4=" + item4.Exchanges, UriKind.RelativeOrAbsolute));
}
}
..............................................................................................................................................................
Update: Additional Code behind Navigation and Selection
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
string name4 = "";
NavigationContext.QueryString.TryGetValue("name4", out name4);
textBox3.Text = string.Format("{0}", name4);
}
..............................................................................................................................................................
public Page2()
{
InitializeComponent();
ListExchanges = new ObservableCollection<ItemsModeling4>();
this.Loaded += new RoutedEventHandler(Page44_Loaded);
}
..............................................................................................................................................................
private void Page44_Loaded(object sender, RoutedEventArgs e)
{
if (ListExchanges.Count == 0)
{
string[] arrangeExchanges = new string[] { "Option1", "Option2", "Option3", "Option4" };
foreach (string item4 in arrangeExchanges)
{
ListExchanges.Add(new ItemsModeling4 { Exchanges = item4 });
}
}
}
public static ObservableCollection<ItemsModeling4> ListExchanges { get; private set; }
..............................................................................................................................................................
public class ItemsModeling4
{
public string Exchanges { get; set; }
}
..............................................................................................................................................................
Xaml
<ListBox x:Name="ListBoxExchanges" Background="Transparent"
Tap="Selection">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Height="Auto" FontFamily="Segoe WP Semibold" FontSize="50" HorizontalAlignment="Left" Margin="12,0,0,0"
Text="{Binding Exchanges}" VerticalAlignment="Top" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What am i doing wrong here?
It Seems like a Master - Detail binding issue. I just don't see where or how to fix it.
The system doesn't give any errors, and I've checked everything with the debugger and everything seems fine.
The listpickers work independently and i don't see where the code might intervene and stop the one from working. It all seems good to me.
help
But if i change the code under the second Else If condition to navigate to Exchanges1 instead of Exchanges2, then the Exchanges1 listpicker displays back the selection into textbox 3, and Exchanges2 does not.
Are you sure that you are comparing culturaly correct strings? Try this compare instead for both of them.
if (String.Equals(a, b, InvariantCultureIgnoreCase))
See StringComparison Enumeration (System) and Best Practices for Using Strings in the .NET Framework for more info.
Otherwise strings can be tricky with spaces. Try
String.Equals(a.Trim(), b.Trim());
to remove any spaces the user may be adding \r\n maybe?
Frankly since the code knows about the choices it may be better to simply provide a ComboBox with the selectable choices for the user instead of using a TextBox.

Contextual action bar set a button to invisible

I have implemented a custom contextual action bar with two buttons : one for deleting selected items from a listview and the other one for editing selected item . What I am trying to do is to make the editButton invisible when two or more items have been selected. I tried doing it this way but nothing happens:
public void OnItemCheckedStateChanged (ActionMode mode, int position, long id, bool check)
{
SetSubtitle (mode);
if (listview.CheckedItemCount > 1) {
disableButtonFlag = true;
} else
disableButtonFlag = false;
self.InvalidateOptionsMenu();
}
public bool OnCreateActionMode (ActionMode mode, IMenu menu)
{
self.MenuInflater.Inflate (Resource.Menu.CAB_menu, menu);
if (disableButtonFlag) {
menu.FindItem(Resource.Id.action_edit).SetVisible(false);
} else {
menu.FindItem(Resource.Id.action_edit).SetVisible(true);
}
mode.Title = "Select Items";
SetSubtitle (mode);
return true;
}
This is how handling multiple items works for me:
private void listView_SelectedIndexChanged(object sender, EventArgs e)
{
if(listView.SelectedIndices.Count > 1)
{
MessageBox.Show("Multiple rows selected!");
}
}
If selected index changes, check for how many indices are checked. If more than 1 (=multiple), fire your code.
Finally I found my mistake! It was that instead of declaring:
if (listview.CheckedItemCount > 1) {
disableButtonFlag = true;
} else
disableButtonFlag = false;
within my OnCreateActionMode method and calling Activity.InvalidateOptionsMenu() in my OnItemCheckedStateChanged()method I should have declared these rows within my OnPrepareActionMode() method and then called ActionMode.Invalidate() within the OnItemCheckedStateChanged()method.

TreeView Nodes appear to be caching

I'm creating a custom web user control in c#. It is intended to interact with a permission hierarchy. We have different "sites" and each site has many "apps" and each app has many "permissions"
So, We have a TabPanel that loads a tab for each site. Then in each tab we have a TreeView where the parent nodes are the apps and the inner nodes are the permissions.
The Permissions show check boxes based on some criteria and are checked based on whether or not the HasPermission function returns true.
All of this code works...but only for the first user selected. For any subsequent user chosen, a step through the debugger shows all the correct logic being executed, but the page displays the same information as that of the first user selected.
So basically, it's saving the display somewhere...and I'm at a loss to find out where.
public partial class Permissions : System.Web.UI.UserControl
{
string _NTLogin;
CoreUser _User;
bool _IsAdmin;
public string NTLogin
{
get
{
return _NTLogin;
}
set
{
ViewState["NTLogin"] = value;
_NTLogin = value;
}
}
public bool IsAdmin
{
get
{
return _IsAdmin;
}
set
{
ViewState["IsAdmin"] = value;
_IsAdmin = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
}
public void LoadTabs()
{
string [] sites = MISCore.BusinessLayer.CorePermission.GetSites();
foreach (string site in sites)
{
TabPanel tp = new TabPanel();
tp.HeaderText = site;
TabContainer1.Tabs.Add(tp);
}
}
public void LoadTrees()
{
if(_User == null)
return;
TabPanelCollection tabs = TabContainer1.Tabs;
foreach (TabPanel tab in tabs)
{
string site = tab.HeaderText;
string[] apps = MISCore.BusinessLayer.CorePermission.GetApplications(site);
TreeView tv1 = new TreeView();
tv1.EnableViewState = false;
foreach (string app in apps)
{
TreeNode tn1 = new TreeNode(app);
tn1.SelectAction = TreeNodeSelectAction.None;
string[] perms = MISCore.BusinessLayer.CorePermission.GetPermissions(site, app);
foreach (string perm in perms)
{
TreeNode tcn1 = new TreeNode(perm);
tcn1.SelectAction = TreeNodeSelectAction.None;
if (IsAdmin || _User.Manager.HasPermission(site, app, perm))
{
tcn1.ShowCheckBox = true;
if (_User.HasPermission(site, app, perm))
{
tcn1.Checked = true;
}
else
{
tcn1.Checked = false;
}
}
else
{
tcn1.ShowCheckBox = false;
}
tn1.ChildNodes.Add(tcn1);
}
tv1.Nodes.Add(tn1);
}
tab.Controls.Add(tv1);
}
}
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
_NTLogin = (string)ViewState["NTLogin"];
_IsAdmin = (bool)ViewState["IsAdmin"];
if(_NTLogin != null)
_User = new CoreUser(_NTLogin);
TabContainer1.Tabs.Clear();
LoadTabs();
LoadTrees();
}
}
[UPDATE]
I iterate through the treeview after all the above code, it correctly stores their correct status. This is an issue with displaying. I can successfully change any other property, tooltip, text, etc to display their state, but the checkboxes are not updating...
I would use Fiddler to see who is caching the results. By looking at the requests you'll be able to tell if it's the browser or the server causing the problem.
Or if its okay with your client, you can put in a small link button that says refresh, and either you or the user can force this refresh treeview method, whenever required.
Should be pretty simple, in the paramters for the tab just add EnableViewState = false. Let me know if this works for you.

Categories

Resources