Proper way to upgrade WPF program settings on program update (Desktop Bridge/Project Centennial) - wpf

When it was a clickonce program it worked, but then I made an appxpackage and exported it as a centennial app for windows store and the upgrade does not work any more.
Right now I have in App.xaml.cs
protected override void OnStartup(StartupEventArgs e) {
if (myprog.Properties.Settings.Default.UpgradeRequired)
{
myprog.Properties.Settings.Default.Upgrade();
myprog.Properties.Settings.Default.UpgradeRequired = false;
myprog.Properties.Settings.Default.Save();
}
With UpgradeRequired as a bool in user settings. Is that the right place?
I am getting settings reset on each version update. Now I have several of these directories
C:\Users\me\AppData\Local\progname\prog.exe_Url_randomChars
each with several different version of program settings. So after the upgrade another one of those is created, instead a subfolder with x.x.x.x of the current version.
As before, on each version release I increase version in Assembly Information the Assembly Version, File Version, and now I have the same numbers in AppxManifest.xml. I keep the last number group of the version to 0 as advised, and just increase the 3rd number group.
Is there something I am missing?

UWP and Desktop Bridge apps need to store their settings in ApplicationData.LocalSettings:
https://learn.microsoft.com/en-us/windows/uwp/design/app-settings/store-and-retrieve-app-data#local-app-data

You could load the previous user.config file into current settings. This is just a workaround, and can be used to transition to ApplicationData.LocalSettings.
public static void Init()
{
if (myprog.Properties.Settings.Default.UpgradeRequired)
{
LoadPreviousSettings(myprog.Properties.Settings.Default);
myprog.Properties.Settings.Default.UpgradeRequired = false;
myprog.Properties.Settings.Default.Save();
}
}
private static void LoadPreviousSettings(params ApplicationSettingsBase[] applicationSettings)
{
const string companyName = "YOUR_COMPANY_NAME_HERE";
var userConfigXml = GetUserConfigXml(companyName);
Configuration config = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.PerUserRoamingAndLocal);
foreach (ApplicationSettingsBase setting in applicationSettings)
{
try
{
// loads settings from user.config into configuration
LoadSettingSection(setting, config, userConfigXml);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("userSettings");
}
catch (FileNotFoundException)
{
// Could not import settings.
// Perhaps user has no previous version installed
}
setting.Reload();
}
}
private static void LoadSettingSection(ApplicationSettingsBase setting, Configuration config, XDocument userConfigXml)
{
string appSettingsXmlName = setting.ToString();
var settings = userConfigXml.XPathSelectElements("//" + appSettingsXmlName);
config.GetSectionGroup("userSettings")
.Sections[appSettingsXmlName]
.SectionInformation
.SetRawXml(settings.Single().ToString());
}
private static XDocument GetUserConfigXml(string companyName)
{
var localPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + $#"\{companyName}";
// previous package folder
var previousLocal = GetDirectoryByWriteTime(localPath, 1);
// previous version, e.g. 1.2.0
var prevousVersion = GetDirectoryByWriteTime(previousLocal, 0);
// application settings for previous version
return XDocument.Load(prevousVersion + #"\user.config");
}
private static string GetDirectoryByWriteTime(string path, int order)
{
var direcotires = new DirectoryInfo(path).EnumerateDirectories()
.OrderBy(d => d.LastWriteTime)
.Reverse()
.ToList();
if (direcotires.Count > order)
{
var previous = direcotires[order];
return previous.FullName;
}
throw new FileNotFoundException("Previous config file not found.");
}

There is a working answer here.
Basically you need to create a duplicate version using UWP's ApplicationData.Settings and then loading it at the beginning of the app. It is very straightforward when your settings are strings, bools, etc. But not so if you have unique settings.
To elaborate more from the answer in the link, when you have settings consisting of custom types/classes, when creating UWP's duplicate version, you can use Newtonsoft.Json to serialise the custom setting:
try
{
ApplicationData.Current.LocalSettings.Values[value.Name] = value.PropertyValue;
}
catch
{
string serialised = JsonConvert.SerializeObject(value.PropertyValue);
ApplicationData.Current.LocalSettings.Values[value.Name] = serialised;
}
Then when loading your custom setting:
if (s.Name == "MyCustomSetting")
{
var deserialised = JsonConvert.DeserializeObject<MyCustomClass>((string)setting.Value);
s.PropertyValue = deserialised;
}

Related

Convert FileInfo read file code to maui equivalent

I know I should know how to do this, but I don't.
I have some code (from a brilliant map routing packing called Itinero http://docs.itinero.tech/index.html) that reads in a a routerDb file.
It works great (in windows) if I use a fully qualified path as it is absolute, but I have moved the file into the Resources.Raw folder and want to read it properly.
The working code
using (var stream = new FileInfo(#"/path/to/my/file/gb.routerdb").OpenRead())
{
routerDb = RouterDb.Deserialize(stream);
}
How can I use the Maui approach to do the same thing? Such as or simpler
using (var stream = new xxx("gb.routerdb").xxxx)
{
routerDb = RouterDb.Deserialize(stream);
}
I'm looking at https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/storage/file-system-helpers?tabs=windows but I don't get it :(
Thanks for any help.
G.
As document Platform differences of File system helpers mentioned, you can use method FileSystem.OpenAppPackageFileAsync to access file in the Resources\Raw folder as a MauiAsset.
Besides, if you want to access the path of the items in folder Raw, you can follow up the unknown issue about this:
https://github.com/dotnet/maui/issues/7943 .
Update:
As a test, I created a simple html(test.html) in folder Resource\Raw, and set BuildAction to MauiAsset.And I used the following code to read the html, it works well on my side(I tested on android device).
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private async Task InitAsync()
{
string filePath = "test.html";
#if WINDOWS
var stream = await FileSystem.OpenAppPackageFileAsync("Assets/" + filePath);
#else
var stream = await FileSystem.OpenAppPackageFileAsync(filePath);
#endif
if (stream != null)
{
string s = (new System.IO.StreamReader(stream)).ReadToEnd();
this.MyWebView.Source = new HtmlWebViewSource { Html = s };
}
}
private void OnClicked(object sender, EventArgs e)
{
InitAsync();
}
}
MainPage.xaml
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<WebView x:Name="MyWebView"
/>
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnClicked"
HorizontalOptions="Center" />
</VerticalStackLayout>
Well I certainly appreciate the example code, but for some reason it didn't work for me, but what did work based on your code (#Jessie Zhang) was:
var stream = await FileSystem.OpenAppPackageFileAsync("gb.routerdb");
if (stream != null)
{
routerDb = RouterDb.Deserialize(stream);
}
The conditional didn't work and I'm not sure why, but I'm only using windows so I'll check that out later. Thanks

Get a third party user installed app icon

I am new to react-native and I seek your help please. What I am planning to do is to get the app icon associated with an app that the user has installed on his device.
I did take a look at this code and realized that I have no way of passing it back to my JS.
Here is what I am doing currently.
private List<String> getNonSystemApps() {
List<PackageInfo> packages = this.reactContext
.getPackageManager()
.getInstalledPackages(0);
List<String> ret = new ArrayList<>();
for (final PackageInfo p: packages) {
if ((p.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("name", p.packageName);
jsonObject.put("firstInstallTime", p.firstInstallTime);
jsonObject.put("installLocation", p.installLocation);
jsonObject.put("applicationInfo", p.applicationInfo);
jsonObject.put("permissions", getPermissionsByPackageName(p.packageName));
Drawable icon = reactContext.getPackageManager().getApplicationIcon(p.packageName);
ret.add(jsonObject.toString());
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return ret;
}
Can you please help me out with this ?
Thanks
Edit
I managed to get it working, based on Aaron's suggestion, I created another private function just to work with the images. This function will generate the base 64 version of an app's icon.
private String getBitmapOfAnAppAsBase64(String packageName) {
if(packageName.isEmpty() ) return new String("");
String base64Encoded = "";
Bitmap bitmap;
try {
Drawable appIcon = this.reactContext.getPackageManager().getApplicationIcon(packageName);
if(appIcon instanceof BitmapDrawable) {
bitmap= ((BitmapDrawable)appIcon).getBitmap();
} else {
bitmap = Bitmap.createBitmap(appIcon.getIntrinsicWidth(), appIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 90, byteArrayOutputStream);
byte[] byteArray = byteArrayOutputStream .toByteArray();
base64Encoded = Base64.encodeToString(byteArray, Base64.DEFAULT);
} catch(Exception e) {
Log.d(TAG,"An error was encounted while getting the package information. The error follows : " + e.toString());
}
return base64Encoded;
}
I then use this generated string in my original function, with the following modifications.
Old Non working code
Drawable icon = reactContext.getPackageManager().getApplicationIcon(p.packageName);
New working code
jsonObject.put("icon", getBitmapOfAnAppAsBase64(p.packageName));
and then in React-Native - its a piece of pie, since it supports base64 already.
"icon" : 'data:image/png;base64,'+installAppObj.icon
Huge thanks to Aaron , for guiding me in the correct direction.
If you haven't already, read through the entire Android Native Modules page. It's not that long and addresses several issues you're likely to run into.
Only these types can be sent to your JS code (via a #ReactMethod):
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
So you effectively have two options:
Encode the Drawable to one of these types, then decode it on the JavaScript side, or
Write the file to disk, and send the path over
I'm not sure what type of Drawable the icon is or if there are guarantees. But any of them should be convertible in some way. For example if it's a Bitmap you can do a Bitmap to Base64 String conversion.

WPF How to create, save and load multiple setting files

In my WPF application I have settings accessed by Properties.Settings.Default.Something.
In these user settings I'm saving different textbox, radiobutton, checkbox values.
I need to have sets of these settings depending on one combobox choice, and saved it. For example, user picks "1" in combobox, sets text in textbox, picks 2, sets text in textbox again. After reopening application I want those textbox values saved. Content of combobox options is generated dynamically.
I know those settings are saved in config file located in Users/appdata/... but I have no clue how and if its even possible to make multiple files like this to be manually saved and loaded on runtime.
Serialize them as xml-file. Here is an generic example how to do this.
Please check out DataContract here
C#
private static T ReadXmlFile<T>(string path) where T : class
{
T result = null;
if (File.Exists(path))
{
try
{
using (XmlReader reader = XmlReader.Create(path))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
result = (T)serializer.ReadObject(reader);
}
}
catch (Exception ex)
{
throw ex; // or what ever
}
}
return result;
}
private static void WriteXmlFile<T>(string path, T content2write) where T : class
{
if (!Directory.Exists(Path.GetDirectoryName(path)))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
using (XmlWriter writer = XmlWriter.Create(path,
new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
Encoding = Encoding.UTF8,
CloseOutput = true
}))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(writer, content2write);
}
}
Maybe save them in your own AppData-folder with Environment.SpecialFolder.LocalApplicationData ;-) and go like this
private static readonly string MyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), #"MyApp\AppDescription");

What is BrowserSubprocessPath?

I have CefSharp-master project with which is Built on Chromium- 31.0.1650.57. All is working fine and perfect. But while Initializing settings.BrowserSubprocessPath is set to an executable.
What is this BrowserSubprocessPath? what happen if I avoid this?
I am Initializing Cef as:
public static void Init()
{
var settings = new CefSettings();
settings.UserAgent = "MyBrowser";
if (!Cef.Initialize(settings))
{
if (Environment.GetCommandLineArgs().Contains("--type=renderer"))
{
Environment.Exit(0);
}
else
{
return;
}
}
}
This is working fine, Only after sometime browser window goes blank.What is the reason behind this.
When you set SingleProcess = false you should define sub-process executable file for it:
http://xilium.bitbucket.org/cefglue/doc/html/E3568E23.htm
So you can set SingleProcess = true (which is not recommended in production)
or set it to a sub-process file like cefclient.exe

MVC5 DisplayModeProvider registration problems

so I have an mvc 5 application with 3 display modes, desktop (default), mobile, and tablet. I'm using WURFL to figure out devices. Here's the code called from global.cs to register:
public static void RegisterDisplayModes(IList<IDisplayMode> displayModes){
var datafile = HttpContext.Current.Server.MapPath(WurflDataFilePath);
var configurer = new InMemoryConfigurer().MainFile(datafile);
var manager = WURFLManagerBuilder.Build(configurer);
HttpContext.Current.Cache[WURFLMANAGER_CACHE_KEY] = manager;
bool mobileEnabled = ConfigurationManager.AppSettings["EnableMobileSite"] == "true";
bool tabletEnabled = ConfigurationManager.AppSettings["EnableTabletSite"] == "true";
var modeDesktop = new DefaultDisplayMode("") {
ContextCondition = (c => c.Request.IsDesktop())
};
var modeMobile = new DefaultDisplayMode("mobile"){
ContextCondition = (c => c.Request.IsMobile())
};
var modeTablet = new DefaultDisplayMode("tablet"){
ContextCondition = (c => c.Request.IsTablet())
};
displayModes.Clear();
if (mobileEnabled) displayModes.Add(modeMobile);
if (tabletEnabled) displayModes.Add(modeTablet);
displayModes.Add(modeDesktop);
}
I'm using some extension methods to HttpRequestBase, as discussed in http://msdn.microsoft.com/en-us/magazine/dn296507.aspx:
public static bool IsDesktop(this HttpRequestBase request){
return true;
}
public static bool IsMobile(this HttpRequestBase request) {
return IsMobileInternal(request.UserAgent) && !IsForcedDesktop(request);
}
public static bool IsTablet(this HttpRequestBase request) {
return IsTabletInternal(request.UserAgent) && !IsForcedDesktop(request);
}
public static void OverrideBrowser(this HttpRequestBase request, bool forceDesktop){
request.RequestContext.HttpContext.Cache[OVERRIDE_BROWSER_CACHE_KEY] = forceDesktop;
}
public static bool IsForcedDesktop(this HttpRequestBase request){
var isForced = request.RequestContext.HttpContext.Cache[OVERRIDE_BROWSER_CACHE_KEY];
return isForced != null ? isForced.ToString().ToBool() : false;
}
private static bool IsMobileInternal(string userAgent) {
var device = WURFLManagerBuilder.Instance.GetDeviceForRequest(userAgent);
if (device.IsTablet() == true) {
return false;
} else {
return device.IsMobile();
}
}
private static bool IsTabletInternal(string userAgent) {
var device = WURFLManagerBuilder.Instance.GetDeviceForRequest(userAgent);
return device.IsTablet();
}
It all works fine for a while, but then after an hour or so, mobile and tablet devices start displaying the desktop views, and the desktop view starts showing the ViewSwitcher shared view (I assume most people are familiar with it, it just allows you to show the desktop view from a mobile device). It's almost like that caching bug in mvc4. I have tried removing my code to register the display modes, and just went with the default mvc mobile support, and it works fine it has the same issue! So clearly there's a problem in here somewhere... can anyone see anything obvious? Almost impossible to debug cause problems only start coming up after a long time, and even then only on the live system really! Any ideas?
Thanks heaps... been on this issue for way too long now...
Cheers
Andy
EDIT: even stripping it right back to the default implementations creates the issue. I added some debugging code to make sure I'm actually running mvc5, but it appears I am. I've also tried the initially recommended workaround for the issue on mvc4 by disabling the cache, still no joy. Is there really no one with info on this?
So I finally figured it out. Very simple as usual. For some reason I used RequestContext.HttpContext.Cache to save the status when someone wants the full view as opposed to the mobile view. I've never used HttpContext.Cache, I'm pretty sure I would have taken that from a blog somewhere - can't find it anymore though. So all that happened was that it would switch the view for everyone, not just the one person. Can't believe it took weeks to figure that out. Hope it helps someone else at some point.

Resources