Any advice or links or sample application (for VS2010) re how to develop a "windowless" WPF application?
That is the ones that look quite modern and don't seem to have the historical window chrome around the edges - they seem to have rounded edges etc...
I wrote a project that did exactly what you are talking about, we used the following project from Microsoft,
http://code.msdn.microsoft.com/WPFShell
Initially I tried writing it myself by turning off the chrome, not a good idea unless you don't want to be able to drag your window around in the standard windows method.
Just remove StartupUri and in the Application Startup method dont load a window:
public partial class App
{
public static bool IsTrue ;
public App()
{
Startup += AppStartup;
}
public void DoWork()
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
Trace.WriteLine("blah");
}
IsTrue = false;
}
void AppStartup(object sender, StartupEventArgs e)
{
IsTrue = true;
new Thread(DoWork).Start();
while (IsTrue)
{ }
Current.Shutdown();
}
}
}
Related
This seems to be a tricky one:
We're working on a Xamarin iOS app that has a UITableView control displaying a list of profiles. Once the user clicks on one of the profiles we'd like to switch to a separate "ProfileViewController" to display more detailed information. This works just fine for 99.9% of the time. In the remaining ~0.1% the item in the list gets highlighted (which happens OnClick) but the app just seemingly freezes and never switches to the other ViewController, leaving the user with an unresponsive list.
The interesting thing to note here is that the app doesn't really freeze, as the simple "swipe back" gesture brings the user back to the UITableView (to be clear the switch-to-profile-viewcontroller animation is never played so it basically switches from the unresponsive list back to the same list, but now it is responsive again).
The tricky thing is that we can't reliably reproduce this bug. It just seems to happen at random, sometimes while stuff is running in the background, other times while the app was previously in an idle state. We are pretty sure that it isn't related to multithreading (we triple-checked everything, using locking and semaphores where necessary), it rather seems to be some rendering issue (as you can still swipe back to the previous screen or at least it isn't your common dead lock).
Using a bunch of Console.WriteLine() tracers and hours of trial and error reproducing this bug we could isolate the problem and discovered the following:
Upon clicking on the list code in ViewDidLoad() and ViewWillAppear() is successfully executed. However ViewDidAppear() never gets invoked. There is nothing else happening in our code or on different threads between ViewWillAppear() and ViewDidAppear() and we don't have any funky / unusual code that gets executed previously (just the usual UI initialization like this in ViewDidLoad()):
...
LblProfileName.TextAlignment = UITextAlignment.Center;
LblProfileName.Text = string.Empty;
LblProfileName.Font = FontAgent.ForSize(30);
LblProfileSlogan.TextAlignment = UITextAlignment.Center;
LblProfileSlogan.Text = string.Empty;
LblProfileSlogan.Font = FontAgent.ForSize(20);
LblProfileRelationInfo.TextAlignment = UITextAlignment.Center;
LblProfileRelationInfo.Text = string.Empty;
LblProfileRelationInfo.Font = FontAgent.ForSize(15);
...
At this point we are kinda out of ideas as to what could be going wrong here and we have independently reviewed any of our code that could remotely be involved in this bug but we found nothing.
We didn't find anything related online but maybe someone else has encountered a similar issue like this in Xamarin / Xamarin iOS before?
Are there any steps we could take that we don't know of? When breaking the app in the Visual Studio for Mac debugger during the freezes the call stacks only contain native code and are not of much use.
Any help or ideas as to what else we could try are hugely appreciated :)
Edit: Adding some code
This is our BaseViewController defining some initialization methods to be used by it's children (again in 99.9% of all cases this works just fine):
public abstract class BaseViewController : UIViewController
{
public BaseViewController(string nibName, NSBundle nSBundle) : base(nibName, nSBundle)
{
ControllerDidInitialize();
}
public ClubmappViewController(IntPtr handle) : base(handle)
{
ControllerDidInitialize();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
CustomOrientationService.ScreenRotationPortrait();
InitializeStaticContent();
Console.WriteLine("ViewDidLoad() exits just fine...");
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Console.WriteLine("When the bug occurs this never gets executed :C");
RefreshUI();
OnViewDidAppear?.Invoke(this, new EventArgs());
LoadData();
}
private protected abstract void InitializeStaticContent();
private protected abstract void RefreshUI();
private protected virtual void LoadData()
{
}
private protected virtual void ControllerDidInitialize()
{
}
}
This is the ProfileViewController that inherits from BaseViewController and that just stops being rendered or inititialized or whatever the actual problem might be (it just never shows up as described above) but most of the time it works just fine:
public partial class ProfileViewController : BaseViewController
{
public const string STORYBOARD_ID = "ProfileViewController";
public ProfileViewController(IntPtr handle) : base(handle)
{
}
private protected override void ControllerDidInitialize()
{
// do initialization things like initializing variables, etc
// no real logic here
}
private protected override void InitializeStaticContent()
{
// layouting loading
LblProfileTitle.TextAlignment = UITextAlignment.Center;
LblProfileTitle.Text = string.Empty;
LblProfileTitle.Font = FontAgent.ForSize(20);
LblProfileTitle.Font = UIFont.BoldSystemFontOfSize(20);
LblProfileName.TextAlignment = UITextAlignment.Center;
LblProfileName.Text = string.Empty;
LblProfileName.Font = FontAgent.ForSize(30);
LblProfileSlogan.TextAlignment = UITextAlignment.Center;
LblProfileSlogan.Text = string.Empty;
LblProfileSlogan.Font = FontAgent.ForSize(20);
// ... and so on ..
}
private protected override void RefreshUI()
{
// ... theme related stuff ...
ViewProfileActive.BackgroundColor = ThemeAgent.CurrentTheme.OnlineIndicatorColor;
LblProfileName.TextColor = ThemeAgent.CurrentTheme.PrimaryTextColor;
LblProfileSlogan.TextColor = ThemeAgent.CurrentTheme.SecondaryTextColor;
// ...
}
private protected async override void LoadData()
{
// load user data ...
ProfileData data = await ...
}
}
And this is the "OnClick" event that is triggered when a profile is clicked on, that's supposed to initialize and show the ProfileViewController (it's unlikely that something's wrong with this but including it nonetheless):
// ...
(sender, e) =>
{
ProfileViewController profileController = (ProfileViewController)UIStoryboard.FromName("Main", null).InstantiateViewController(ProfileViewController.STORYBOARD_ID);
profileController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
currentViewController.NavigationController.PushViewController(profileController, true);
}
// ...
I have recently made a Class Library (dll) for my other project to program a Bluetooth device via serial port (COM). The library is used to transfer firmware via COM port. It works fine until the requirement comes, which requires a WPF window to show the progress of programming. I have successfully created the progress bar using standard WPF app template. However, the standard WPF does not allow me to generate dll. After searching here, I found this link that teaches you how to add a WPF window to existing Class Library project. Also, someone teaches you how to show the window from here. Everything look good until I tried, there is nothing shows up when I call the method ProgrammBluetooth() from LabVIEW.
My main method, which is in a separate .cs file:
namespace BTMProg
{
public class BTMProgrammer
{
private bool _uut1Status = false;
private string _uut1Message = "";
public bool UUT1Status
{
get { return _uut1Status; }
set { _uut1Status = value; }
}
public string UUT1Message
{
get { return _uut1Message; }
set { _uut1Message = value; }
}
public void ProgramBluetooth (string ioPort, string firmwareFile)
{
List<UUT> uutList = new List<UUT>();
uutList.Add(new UUT(ioPort, "UUT1", 1));
Thread thread = new Thread(() =>
{
var wn = new MainWindow(uutList, firmwareFile);
wn.ShowDialog();
wn.Closed += (s, e) => wn.Dispatcher.InvokeShutdown();
Dispatcher.Run();
if (wn.TaskList[0].Result.ToUpper().Contains("SUCCESS"))
{
_uut1Status = true;
_uut1Message = wn.TaskList[0].Result;
}
else
{
_uut1Status = false;
_uut1Message = wn.TaskList[0].Result;
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
}
My WPF code in MainWindow.xaml.cs:
ProgrammingViewModel _pvm = new ProgrammingViewModel();
private List<string> _viewModeList = new List<string>();
private List<Task<string>> _taskList = new List<Task<string>>();
public List<Task<string>> TaskList {
get => _taskList;
set => _taskList = value;
}
public MainWindow(List<UUT> uutList, string firmwareFile)
{
InitializeComponent();
foreach (var uut in uutList)
{
_viewModeList.Add(uut.UutName);
}
_pvm.AddProcessViewModels(_viewModeList);
ProgressBarView.DataContext = _pvm.ProcessModels;
StartProgramming(uutList, firmwareFile);
Application.Current.MainWindow.Close();
}
The issue before was that if I don't use dispatcher to create a new thread, an exception saying "The calling thread must be STA, because many UI components require this...." thrown. After I use the new thread, no error but the window does not show up as expected. What could be the problem? Thanks.
The ShowDialog function will stop execution of the thread until the window closes, meaning the rest of that code may not run and the dispatcher may not be started. You should try the Show method instead, which returns as soon as the window is shown.
Also, what is going on with these lines in the constructor of the window?
StartProgramming(uutList, firmwareFile);
Application.Current.MainWindow.Close();
Whatever that first line does, it needs to return and not do a bunch of work if you want the window to finish getting constructed. The second line makes no sense at all. Why are you closing the main window of the application? Did you even set and open a window associated with that property at some point?
I suspect one or more of these things is preventing the thread from ever reaching the point where it can show the window.
I've got a little problem and tried to solve it now for nearly 6 to 8 hours but I didn't find any matching answer. I'm a complete newbie to WPF, so please point me any errors I made.
At first I have the following in my App.xaml.cs:
namespace WpfVideowand
{
public partial class App : Application
{
...
private void Application_Startup(object sender, StartupEventArgs e)
{
foreach (System.Windows.Forms.Screen MyScreen in System.Windows.Forms.Screen.AllScreens)
{
List<string> MyStrings = Xml.GetScreens(i);
if (MyStrings[1] == "true")
{
OpenWindow(MyScreen, MyStrings[0], i);
}
i++;
Shelf MyShelf = new Shelf(MyScreen, i, MyStrings[0]);
MyShelf.Show();
}
}
private void OpenWindow(System.Windows.Forms.Screen myScreen, string configName, int screenNumber)
{
Shelf NewShelf = new Shelf(myScreen, screenNumber, configName);
}
}
}
And inside the Shelf.xaml.cs it looks this way:
namespace WpfVideowand
{
public partial class Shelf : Window
{
[DllImport("user32.dll")]
static extern IntPtr GetActiveWindow();
System.Windows.Forms.Screen _Screen { get; set; }
...
public Shelf(System.Windows.Forms.Screen myScreen, int screenNumber, string configName)
{
InitializeComponent();
_Screen = myScreen;
ShowOnMonitor(screenNumber);
...
}
private void ShowOnMonitor(int screenNumber)
{
System.Windows.Forms.Screen[] ScreenArray;
ScreenArray = System.Windows.Forms.Screen.AllScreens;
int XCoord = Convert.ToInt32(ScreenArray[screenNumber].Bounds.Left);
this.Left = XCoord;
int YCoord = Convert.ToInt32(ScreenArray[screenNumber].Bounds.Top);
this.Top = XCoord;
IntPtr active = GetActiveWindow();
System.Windows.Application.Current.Windows.OfType<Window>().SingleOrDefault(window => new WindowInteropHelper(window).Handle == active).Name = "Monitor" + screenNumber.ToString();
System.Windows.Application.Current.Windows.OfType<Window>().SingleOrDefault(window => new WindowInteropHelper(window).Handle == active).WindowState = WindowState.Maximized;
}
...
}
}
The way described above worked fine in Windows Forms Application. In WPF I have the problem, that I get the error message, that rectangle (the window) would have no Top or Left property.
I even tried it in some other ways, like creating with
System.Windows.Forms.Screen _screen = System.Windows.Forms.Screen.FromControl(this);
an object, that would have .Top and .Left. But there I get the message, that I cannot convert a Shelf-object into a System.Windows.Forms.Control.
Anyone a suggestion, how I could make my Screens appear on the monitor where it should be?
Ok, I found it myself...
For anyone who is interested in finding some answers to this problem here it is:
At first, see that you have the correct reference implemented. For this you need System.Drawing and System.Windows.Forms. (Afterwards you have to declare many things explicit, like System.Windows.Controls.Button instead of Button, etc.)
Then see that you start the app with something like Startup="Application_Startup" and not an uri, because you want to start many forms and not only one.
Afterwards be absolutly sure to NOT set the Windowstyle to maximized in the XAML (this did cost me nearly 4 hours. In between i grew 56 grey hairs). Use this in the Code-Behind:
System.Windows.Application.Current.Windows.OfType().SingleOrDefault(window => new WindowInteropHelper(window).Handle == active).WindowState = WindowState.Maximized;
I have an applications that install two other application which has a "Help" option. Each of these application has a common help file but the contents should be displayed based on the index selected for the application in the "Table of Contents". If i open one application, the help of that particular application should be displayed.
My code looks like this for Appl1.
private void Help_Click(Core.CommandBarButton Ctrl, ref bool CancelDefault)
{
if (System.IO.File.Exists(new PlugInConstants().HELP_FILE_Path))
{
System.Windows.Forms.Help.ShowHelp(new System.Windows.Forms.Control(),
new PlugInConstants().HELP_FILE_Path,
System.Windows.Forms.HelpNavigator.TableOfContents, "Appl1");
}
else
{
System.Windows.Forms.MessageBox.Show(m_objLanguage.ERR_HELP_NOT_FOUND.Replace
("%1", m_objGlobalConfig.HelpFilename));
}
CancelDefault = false;
}
and looks like this for Appl2
private void HelpToolStripMenuItem_Click(object sender, EventArgs e)
{
helpToolStripMenuItem.Enabled = false;
string helpFilePath;
helpFilePath = new TrayConstants().HELP_FILE_Path;
if (System.IO.File.Exists(helpFilePath))
{
System.Windows.Forms.Help.ShowHelp(new System.Windows.Forms.Control(),
helpFilePath, System.Windows.Forms.HelpNavigator.TableOfContents, "Appl2") ;
}
else
{
if (m_helpPage == null)
m_helpPage = new HelpPage();
m_helpPage.ShowDialog();
}
helpToolStripMenuItem.Enabled = true;
}
from this i can only see the Content page of common helpfile, but not the particular application help that is selected.
Now i did run Appl1 but still i can see the main MyAppbut not Appl1that is automatically selected and the contents that is displayed to right.
I am using VS 2010,C#, win forms
thanks in advance
I believe that your issue is that you are accessing the wrong value in the HelpNavigator enum. Looks like it should be Topic, not TableOfContents.
System.Windows.Forms.Help.ShowHelp(new System.Windows.Forms.Control(),
helpFilePath, System.Windows.Forms.HelpNavigator.Topic, "Appl2") ;
http://msdn.microsoft.com/en-us/library/system.windows.forms.helpnavigator.aspx
I want to create an app that behaves as follows:
On no argument it displays the main form
On argument "a" does a job but the main form isn't loaded.
On argument "b" the form loads using the argument passed (load that document)
For the 1 and 3 I can handle the arguments in the form's constructor as follows:
public ConfigurationActionManagerForm()
{
InitializeComponent();
Environment.GetCommandLineArgs();
// do stuff with that argument
}
But this approach doesn't allow me to apply the behavior of 2. in the list.
In program.cs I can edit it to handle the arguments before the form is even created, but what is the correct approach on using Application.Run() if I don't want to pass a form? How am I going to inform Program class instance that I need to terminate or show a message that something went wrong or even show a little taskbar icon that the process is doing stuff (Think of it like the unzipping process).
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ConfigurationActionManagerForm());
}
Would this approach from MSDN be correct to my application?
Do you mean in the same way that Visual Studio works?
If so then you can't do this in a normal Windows application - Visual Studio cheats.
The problem is that a Windows application can either be a Windows Forms application or a Console application, but it can't be both - its decided at compile time (for .Net applications this is in the project properties window). Your options are:
Make your application a Windows Forms application
In this case #1 and #3 will work perfecty, but for #2 you will find that you can't read from / write to the console (because there isn't one!). If your appliction doesn't need to give any feedback then this might be fine - do your work as you normally would and just don't display a form:
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
// Handle #2 here
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ConfigurationActionManagerForm());
}
}
Make your application a console application
In this case #2 will work perfectly, however although #1 and #3 will work fine you will always have console window open in the background - if you close the console window your application will end.
Again this might be fine, but personally I find this to be a hack.
Cheat (do what Visual Studio Does)
Visual Studio cheats by having 2 separate applications - one is a Console Application and the other is a Windows Forms application. The easy solution is to leave it at that and require that users start a different executable when running the command line version (e.g. myprogram_g.exe and myprogram_w.exe).
Visual Studio goes one step further however and has a single entry point, devenv. It does this by using the fact that for compatability reasons the Windows shell will always run a .com file instead of a .exe if there is any ambiguity. Wheras all shortcuts etc.. point to the executable, if you run devenv on the command line the devenv.com application will run instead which uses magic to sort out whether or not it runs as a console or windows application.
My advice would be to create two different applications and leave it at that.
See How do I write a program that can be run either as a console or a GUI application? for more detail (make sure to read the comments which have additional useful suggestions).
Also see How to make an application as both GUI and Console application? for how ildasm does this.
You can call Application.Run() without a form instance.
That way, it will start the message loop without opening a form.
You can call MessageBox.Show() before calling .Run(), too.
You can even create and open a form, and then call Run() without specifying an argument - it just means that closing the form doesn't automatically exit the application.
E.g.
MessageBox.Show("Messaage!");
Form1 f = new Form1();
f.Show();
Application.Run();
As stated above, this way of doing Run() means that closing the forms doesn't automatically close the application. You need to handle this in the form's Close event handler. (Application.Exit())
MSDN online can help you out with this - check the help entry for Application.Run().
Basically you want a console applcation with a few changes.
Here's an example of how to get started, using a default aboutbox class:
using System;
using System.Windows.Forms;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("No Arguments");
}
else
{
if (args[0] == "a")
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new AboutBox1());
}
}
}
}
}
And AboutBox1 class:
using System.Reflection;
using System.Windows.Forms;
namespace ConsoleApplication1
{
partial class AboutBox1 : Form
{
public AboutBox1()
{
InitializeComponent();
this.Text = String.Format("About {0} {0}", AssemblyTitle);
this.labelProductName.Text = AssemblyProduct;
this.labelVersion.Text = String.Format("Version {0} {0}", AssemblyVersion);
this.labelCopyright.Text = AssemblyCopyright;
this.labelCompanyName.Text = AssemblyCompany;
this.textBoxDescription.Text = AssemblyDescription;
}
#region Assembly Attribute Accessors
public string AssemblyTitle
{
get
{
object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
if (attributes.Length > 0)
{
AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0];
if (titleAttribute.Title != "")
{
return titleAttribute.Title;
}
}
return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase);
}
}
public string AssemblyVersion
{
get
{
return Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
}
public string AssemblyDescription
{
get
{
object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false);
if (attributes.Length == 0)
{
return "";
}
return ((AssemblyDescriptionAttribute)attributes[0]).Description;
}
}
public string AssemblyProduct
{
get
{
object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false);
if (attributes.Length == 0)
{
return "";
}
return ((AssemblyProductAttribute)attributes[0]).Product;
}
}
public string AssemblyCopyright
{
get
{
object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
if (attributes.Length == 0)
{
return "";
}
return ((AssemblyCopyrightAttribute)attributes[0]).Copyright;
}
}
public string AssemblyCompany
{
get
{
object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
if (attributes.Length == 0)
{
return "";
}
return ((AssemblyCompanyAttribute)attributes[0]).Company;
}
}
#endregion
private void okButton_Click(object sender, EventArgs e)
{
Close();
}
}
}
I found a neat and simple to implement solution using the example in my question provided by microsoft.
I created this application context class that is responsible for everything in the application and I use this instead of a form in the Application.Run() as shown below.
To achieve the behavior in the question, I am using a second form that is hidden and only the taskbar icon is shown. If the user wants to see how the process is doing, they can click the taskbar icon and see the logging window, which is actually the ConfigurationApplierForm in the example bellow.
class AnApplicationContext: ApplicationContext
{
private Form _currentForm;
Note the constructor is private, the main is inside this class and declared static.
private AnApplicationContext()
{
Application.ApplicationExit += new EventHandler(this.OnApplicationExit);
// choose which form to show based on arguments
if(Environment.GetCommandLineArgs().Contains("-apply"))
{
_currentForm = new ConfigurationApplierForm();
}
else
{
_currentForm = new ConfigurationActionManagerForm();
}
// initialize the form and attach event handlers
_currentForm.FormClosed += new FormClosedEventHandler(this.OnCurrentFormClosed);
_currentForm.ShowDialog();
}
Main is here, a little bit different from the original. Notice the argument in the Run method
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// context is passed instead of a form
Application.Run(new AnApplicationContext());
}
private void OnCurrentFormClosed(object sender, EventArgs e)
{
ExitThread();
}
private void OnApplicationExit(object sender, EventArgs e)
{
/* is there anything to do when all forms are closed
and the application is going to die?*/
}
}
Also, we need to tell the project that this is the startup project.
Project Properties -> Application -> Startup Project