I'm trying to make my application force a theme - this is straightforward as shown here: http://arbel.net/blog/archive/2006/11/03/Forcing-WPF-to-use-a-specific-Windows-theme.aspx
However, I don't know what theme I'm using now. I'm using the Windows XP default theme, whatever that may be. That article says
It's important to specify the version
and the public key token
...where do I get that information?
To get the theme name you can call the unmanaged GetCurrentThemeName method:
public string GetThemeName()
{
StringBuilder themeNameBuffer = new StringBuilder(260);
var error = GetCurrentThemeName(themeNameBuffer, themeNameBuffer.Capacity, null, 0, null, 0);
if(error!=0) Marshal.ThrowExceptionForHR(error);
return themeNameBuffer.ToString();
}
[DllImport("uxtheme.dll", CharSet=CharSet.Auto)]
public static extern int GetCurrentThemeName(StringBuilder pszThemeFileName, int dwMaxNameChars, StringBuilder pszColorBuff, int dwMaxColorChars, StringBuilder pszSizeBuff, int cchMaxSizeChars);
You can find the version and public key token by right-clicking the theme .dll (such as PresentationFramework.Aero) in the GAC (open c:\Windows\Assembly in Exporer), or you can use code to do it. Just loop through all loaded assemblies using AppDomain.CurrentDomain.LoadedAssemblies and find the one you want:
foreach(Assembly a in AppDomain.CurrentDomain.LoadedAssemblies)
if(a.Name.StartsWith("PresentationFramework."))
return a.FullName;
Note that looping through the loaded assemblies will also tell you the current theme name if only one theme has been loaded in the current AppDomain.
Related
We use external component (MigraDoc) to compose an RTF document. Which then is converted to plain text by assigning RTF as string to System.Windows.Forms.RichTextBox's Rtf field and reading Text field. This has worked earlier but now we have found a problem (which has been there for a while already).
Plain text conversion is not working on Windows 10 but same application is working on Windows 7. After assigning Rft field, the Text field remains empty and also Rft field doesn't have the value which was just assigned. *
However, earlier version of our application is working on Windows 10 as well. Even there are no direct constitutive changes on this area. One possibly affecting change is .Net target version change from 4.0 to 4.7.2 (but it is hard to verify this anymore).
If I take the RTF string from Windows 7 and save it as file, it opens on WordPad on Windows 7. But it doesn't open on WordPad on Windows 10.
Have somebody else phased similar issues? Or are there any ideas how this could be fixed?
* But instead value:
{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Microsoft Sans Serif;}}
{\*\generator Riched20 10.0.19041}\viewkind4\uc1
\pard\f0\fs17\par
}
EDIT:
MigraDoc version is 1.32 i.e. the latest non-beta.
If you want to try out the RICHEDIT20W version of RichEdit Control (Rich Text Edit Control v. 3.1), use a Custom Control built like this one. It tries to load the riched20.dll library and, if it succeeds, it then sets the Class name of the Control in the CreateParams override.
You could also try to load the RICHEDIT60W version that is usually shipped with MS Office installations, for testing. This version has also different behavior.
In this case, you have to provide the full path of the library, which depends on the installed Office version and bitness
In practice, you have means to use a specific version of the Control.
Tweak this code to make it act as you prefer. As it is, it allows to switch between ver. RICHEDIT20W and RICHEDIT50W (design-time or run-time)
using System.ComponentModel;
using System.Runtime.InteropServices;
public class RichTextBox20W : RichTextBox {
private bool m_UseRichedit20 = true;
public RichTextBox20W() {
IsRichEdit20Available = LoadLibrary("riched20.dll") != IntPtr.Zero;
}
[DefaultValue(true)]
public bool UseRichedit20 {
get => m_UseRichedit20 & IsRichEdit20Available;
set {
if (value != m_UseRichedit20) {
m_UseRichedit20 = value;
RecreateHandle();
}
}
}
public bool IsRichEdit20Available { get; }
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
if (UseRichedit20) {
cp.ClassName = "RICHEDIT20W";
}
// If the library is not found, the class name is set to RICHEDIT50W
// which is the default when targeting .NET Framework 4.7.2+
return cp;
}
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr LoadLibrary(string lpLibFileName);
}
I published a C# winforms GUI and everything looks as expected on my machine. I went to install on another machine and all my text gets italicized.
Both machines are running windows 10 and have the same screen resolution settings. I also installed my GUI on a third machine and everything works as expected on it.
Is there some setting in Visual Studio I have to set for the fonts to look the same on all machines? Or is there a specific code I need to add?
Here is a snippet of what the GUI should look like (no italics)
GUI on machine #2 (font gets italicized)
Ok so that you could close the question:
Check the Font Settings and ensure the font you want is installed.
There.
Ok so after a ton of SO and Google based on the comments received earlier, I was able to embed the font into my code. Now, on startup, my app checks if the font is installed on the machine. If font is not installed, my app installs it.
This way eliminates having to manually check if the font is installed on each machine that the app will be run on.
Note: need admin privileges for this to work.
First: Embed the font file as a resource
Double-click Resources.resx, and in the toolbar for the designer click Add Resource/Add Existing File and select your .ttf file
In the solution explorer, right-click your .ttf file and go to Properties. Set the 'Build Action' to "Content" and 'Copy To Output Directory' property set to "Copy Always"
Second: Add this code
using Microsoft.Win32;
using System;
using System.Drawing.Text;
using System.IO;
using System.Runtime.InteropServices;
namespace TestAutomation
{
public partial class SplashScreen : Form
{
[DllImport("gdi32.dll", EntryPoint = "AddFontResource")]
public static extern int AddFontResource(string lpFileName);
[DllImport("gdi32.dll")]
private static extern int CreateScalableFontResource(uint fdwHidden, string
lpszFontRes, string lpszFontFile, string lpszCurrentPath);
// <summary>
// Installs font on the user's system and adds it to the registry so it's available on the next session
// Your font must be embedded as a resource in your project with its 'Build Action' property set to 'Content'
// and its 'Copy To Output Directory' property set to 'Copy Always'
// </summary>
private void RegisterFont(string contentFontName)
{
DirectoryInfo dirWindowsFolder = Directory.GetParent(Environment.GetFolderPath(Environment.SpecialFolder.System));
// Concatenate Fonts folder onto Windows folder.
string strFontsFolder = Path.Combine(dirWindowsFolder.FullName, "Fonts");
// Creates the full path where your font will be installed
var fontDestination = Path.Combine(strFontsFolder, contentFontName);
// Check if file exists in destination folder. If not, then copy the file from project directory to destination
if (!File.Exists(fontDestination))
{
try
{
// Copies font to destination
File.Copy(Path.Combine(Directory.GetCurrentDirectory(), contentFontName), fontDestination);
// Retrieves font name
PrivateFontCollection fontCol = new PrivateFontCollection();
fontCol.AddFontFile(fontDestination);
var actualFontName = fontCol.Families[0].Name;
// Add font
AddFontResource(fontDestination);
// Add registry entry
Registry.SetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts",
actualFontName, contentFontName, RegistryValueKind.String);
}
catch (Exception e)
{
MessageBox.Show(e.Message + "\n\nThe required font(s) have not been installed."
+ "\n\nPlease contact your systems administrator for help.");
Start();
}
}
// If file exists in destination folder, then start program.
else
{
Start();
}
}
public SplashScreen()
{
RegisterFont("GOTHIC.TTF");
}
private void Start()
{
InitializeComponent();
}
}
}
I modified the code to fit my needs. Here are the links to where I found my info:
How to quickly and easily embed fonts in winforms app in C#
https://csharp.hotexamples.com/examples/System.Drawing.Text/PrivateFontCollection/AddFontFile/php-privatefontcollection-addfontfile-method-examples.html
I started running into a small problem regarding debugging my program. It would start off showing no errors, then I would press debug to test it. It would throw me an error saying
"Could not copy the file "obj\x86\Debug[programName].exe" because it was not found."
I have proceeded to tamper with various things, and came to the conclusion that it was a class that I am using to read ini files by importing a dll. The two most likely lines in that class are these:
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
To test this, I removed the class and all references from the project, and it would build the project successfully. Then, I could look at the path and find the executable that was previously missing. Thinking the problem fixed, I put the class back in and as you would expect, it broke with the same error. It actually deleted the executable file before building, so in my head, it is obvious why it couldn't be found.
However, the not so obvious part is: The program was building and executing with this class in it up until this morning with no changes performed on it. Plus, the class works perfectly inside of my Unity3D game where it is reading the ini file this c# program creates.
Can anyone tell me why this is happening, and if there is a fix to it? I already tried creating a new project and re-importing everything, and it produces the same errors.
EDIT
After commenting and uncommenting each line, I found that the three commented lines in this are causing the problem:
public bool SaveToIni()
{
IniFile file = new IniFile("/LoadUpSettings.ini");
try
{
file.IniWriteValue("Screen", "Screen Height", cbbScreenHeight.SelectedItem.ToString());
file.IniWriteValue("Screen", "Screen Width", cbbScreenWidth.SelectedItem.ToString());
//file.IniWriteValue("Controllers", "Razer Hydra", ckbRazerHydra.Checked.ToString());
//file.IniWriteValue("Controllers", "Oculus Rift", ckbOculusRift.Checked.ToString());
//file.IniWriteValue("Screen", "Fullscreen", ckbFullscreen.Checked.ToString());
return true;
}
catch
{
MessageBox.Show("Please fill out all values");
return false;
}
}
This is the IniWriteValue function inside the IniFile class.
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
public string IniReadValue(string Section, string Key)
{
StringBuilder temp = new StringBuilder(255);
int i = GetPrivateProfileString(Section, Key, "", temp, 255, this.path);
return temp.ToString();
}
Oddly enough, if i remove the ckbRazerHydra.Checked.ToString(), and put in a standard string such as "True", it still doesn't work, although with those 3 lines commented out, the project builds completely.
EDIT
I managed to fix this problem to an extent. I just have to run my program in release version. If I run it in debug mode, I will always get the error saying the exe couldn't be copied because it wasn't found. However, Release mode seems to almost always work.
The bin and obj folders are meant for output only. When you tell Visual Studio to do a clean or a rebuild it will delete all files in these folders. You can safely delete these folders at any time and you shouldn't lose anything in the process.
You are never meant to place any files in these folders. If you want to add an external assembly (EXE or DLL) to your project you should add it to your project using the Add->Existing Item command on a project. Then you can tell your project to reference that file and it will use the local relative path.
For example, if you create a "lib" folder in your project root and place some.dll inside it, you can then add a reference to the file located in your project and it will use the relative path ..\lib\some.dll.
The problem is with files in your current project that you have set to
Copy to output directory=copy always/copy if newer
When you add a file this way VS will delete all other previous files that other projects dumped into your current project bin/.
In order to avoid this situation add the files and leave them with Copy to output directory=Do not copy option and then use MSBuild Copy task to dump your files.
If you are using docker you can simple put a COPY command - the same thing but not with MSBuild.
I believe this is the default behavior of some this MSBuild internal task and a workaround is the only option.
After reading Larry Osterman's response on the very same issue I am trying to solve at the moment, I thought I had found the answer to my question.
For the record, the question was : how can I from .Net (non-WinRT) list the types in a WinRT assembly ( mine are .dll files apparently, not .Winmd)
I therefore used the following code snippet :
//note, this wrapper function returns the metadata file name and token
// it immediately releases the importer pointer
static Tuple<string, UInt32> ResolveTypeName(string typename)
{
string path;
object importer = null;
UInt32 token;
try
{
var hr = RoGetMetaDataFile(typename, IntPtr.Zero, out path, out importer, out token);
//TODO: check HR for error
return Tuple.Create(path, token);
}
finally
{
Marshal.ReleaseComObject(importer);
}
}
[DllImport("WinTypes.dll")]
static extern UInt32 RoGetMetaDataFile(
[MarshalAs(UnmanagedType.HString)] string name,
IntPtr metaDataDispenser,
[MarshalAs(UnmanagedType.HString)] out string metaDataFilePath,
[MarshalAs(UnmanagedType.Interface)] out object metaDataImport,
out UInt32 typeDefToken);
( found on https://gist.github.com/2920743)
Unfortunately, I get a non-zero HResult.
I referred to the documentation and found this :
HR_RESULT_FROM_WIN32(ERROR_NO_PACKAGE) The function was called from a
process that is not in a Windows Store app.
Does that mean it is not possible to list the types from .Net (non-WinRT) at all ?
RoGetMetaDataFile is used to load a metadata file from within an app package. It locates the metadata file in which the named type is defined, loads that metadata file, and returns an IMetaDataImport interface pointer that represents that metadata file.
From ordinary .NET code you can call RuntimeEnvironment.GetRuntimeInterfaceAsIntPtr (or GetRuntimeInterfaceAsObject) to get the current runtime's IMetaDataDispenser interface pointer, which can be used to load arbitrary modules for inspection.
From native code, you can call ICLRMetaHost::GetRuntime to load a runtime, then from that object call ICLRRuntimeInfo::GetInterface to get its IMetaDataDispenser interface pointer.
RoGetMetaDataFile can be used from outside the app package, however it will only resolve system windows runtime types.
In order to resolve app specific types, you need to be running with "package identity" - in other words, in the context of a running application.
So I'm trying to do manually what AxImp does (I'm doing it dynamically).
My product is a wide released, sanctioned "add-on" to a third party product. They have an OCX, which I add to my form with a COM reference...however, if the client has (or installs) an updated version of their product my product can no longer load the OCX.
Therefore I'm trying to load their OCX dynamically. I've got everything working except that I need the GUID of one of the interfaces in one of their OCXs. I know what the type name is, and the OCX >is< registered on the system. How can I get an object's GUID just from the type name?
Note, Assembly.LoadFrom() doesn't work because the OCX isn't .NET it's COM.
Since your comment let us know that the GUID is found in the OCX but not registered under HKEY_CLASSES_ROOT, we'll have to read it from the type library:
Call LoadTypeLib or LoadTypeLibEx, passing the path to the .OCX file
Then use the FindName method of the returned object.
Then GetTypeAttr followed by PtrToStructure to get a TYPEATTR structure with the GUID.
Not sure why you can't simply add a COM reference to the DLL to your project.
Visual Studio will automatically add a .NET wrapper to any COM object that is referenced this way.
I had to write something like that to migrate code from COM to C#
class GetGuid
{
[DllImport("oleaut32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
static extern int LoadTypeLib(string fileName, out ITypeLib typeLib);
public string SearchRegistry(string dllPath /* or ocx */)
{
var result = string.Empty;
ITypeLib tl;
if (LoadTypeLib(dllPath, out tl) == 0)
{
ITypeInfo tf;
tl.GetTypeInfo(0, out tf);
var ip = IntPtr.Zero;
tl.GetLibAttr(out ip);
if (ip != IntPtr.Zero)
{
var ta = (System.Runtime.InteropServices.ComTypes.TYPELIBATTR)Marshal.PtrToStructure(ip, typeof(System.Runtime.InteropServices.ComTypes.TYPELIBATTR));
result = ta.guid.ToString();
}
}
return result;
}
}