OK, this is a slightly weird question.
We have a touch-screen application (i.e., no keyboard). When users need to enter text, the application shows virtual keyboard - hand-built in WinForms.
Making these things by hand for each new language is monkey work. I figure that windows must have this keyboard layout information hiding somewhere in some dll. Would there be anyway to get this information out of windows?
Other ideas welcome (I figure at least generating the thing from a xml file has got to be better than doing it by hand in VS).
(Note: having said all which, I note that there is a Japanese keyboard, state machine and all..., so XML might not be sufficient)
UPDATE: pretty good series on this subject (I believe) here
Microsoft Keyboard Layout Creator can load system keyboards and export them as .klc files. Since it’s written in .NET you can use Reflector to see how it does that, and use reflection to drive it. Here's a zip file of .klc files for the 187 keyboards in Windows 8 created using the below C# code. Note that I originally wrote this for Windows XP, and now with Windows 8 and the on-screen keyboard, it is really slow and seems to crash the taskbar :/ However, it does work :)
using System;
using System.Collections;
using System.IO;
using System.Reflection;
class KeyboardExtractor {
static Object InvokeNonPublicStaticMethod(Type t, String name,
Object[] args)
{
return t.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)
.Invoke(null, args);
}
static void InvokeNonPublicInstanceMethod(Object o, String name,
Object[] args)
{
o.GetType().GetMethod(name, BindingFlags.Instance |
BindingFlags.NonPublic) .Invoke(o, args);
}
static Object GetNonPublicProperty(Object o, String propertyName) {
return o.GetType().GetField(propertyName,
BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(o);
}
static void SetNonPublicField(Object o, String propertyName, Object v) {
o.GetType().GetField(propertyName,
BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(o, v);
}
[STAThread] public static void Main() {
System.Console.WriteLine("Keyboard Extractor...");
KeyboardExtractor ke = new KeyboardExtractor();
ke.extractAll();
System.Console.WriteLine("Done.");
}
Assembly msklcAssembly;
Type utilitiesType;
Type keyboardType;
String baseDirectory;
public KeyboardExtractor() {
msklcAssembly = Assembly.LoadFile("C:\\Program Files\\Microsoft Keyboard Layout Creator 1.4\\MSKLC.exe");
utilitiesType = msklcAssembly.GetType("Microsoft.Globalization.Tools.KeyboardLayoutCreator.Utilities");
keyboardType = msklcAssembly.GetType("Microsoft.Globalization.Tools.KeyboardLayoutCreator.Keyboard");
baseDirectory = Directory.GetCurrentDirectory();
}
public void extractAll() {
DateTime startTime = DateTime.UtcNow;
SortedList keyboards = (SortedList)InvokeNonPublicStaticMethod(
utilitiesType, "KeyboardsOnMachine", new Object[] {false});
DateTime loopStartTime = DateTime.UtcNow;
int i = 0;
foreach (DictionaryEntry e in keyboards) {
i += 1;
Object k = e.Value;
String name = (String)GetNonPublicProperty(k, "m_stLayoutName");
String layoutHexString = ((UInt32)GetNonPublicProperty(k, "m_hkl"))
.ToString("X");
TimeSpan elapsed = DateTime.UtcNow - loopStartTime;
Double ticksRemaining = ((Double)elapsed.Ticks * keyboards.Count)
/ i - elapsed.Ticks;
TimeSpan remaining = new TimeSpan((Int64)ticksRemaining);
String msgTimeRemaining = "";
if (i > 1) {
// Trim milliseconds
remaining = new TimeSpan(remaining.Hours, remaining.Minutes,
remaining.Seconds);
msgTimeRemaining = String.Format(", about {0} remaining",
remaining);
}
System.Console.WriteLine(
"Saving {0} {1}, keyboard {2} of {3}{4}",
layoutHexString, name, i, keyboards.Count,
msgTimeRemaining);
SaveKeyboard(name, layoutHexString);
}
System.Console.WriteLine("{0} elapsed", DateTime.UtcNow - startTime);
}
private void SaveKeyboard(String name, String layoutHexString) {
Object k = keyboardType.GetConstructors(
BindingFlags.Instance | BindingFlags.NonPublic)[0]
.Invoke(new Object[] {
new String[] {"", layoutHexString},
false});
SetNonPublicField(k, "m_fSeenOrHeardAboutPropertiesDialog", true);
SetNonPublicField(k, "m_stKeyboardTextFileName",
String.Format("{0}\\{1} {2}.klc",
baseDirectory, layoutHexString, name));
InvokeNonPublicInstanceMethod(k, "mnuFileSave_Click",
new Object[] {new Object(), new EventArgs()});
((IDisposable)k).Dispose();
}
}
Basically, it gets a list of all the keyboards on the system, then for each one, loads it in MSKLC, sets the "Save As" filename, lies about whether it's already configured the custom keyboard properties, and then simulates a click on the File -> Save menu item.
It is a fairly well-known fact that MSKLC is unable to faithfully import & reproduce keyboard layouts for all of the .DLL files supplied by Windows–especially those in Windows 8 & above. And it doesn't do any good to know where those files are if you can't extract any meaningful or helpful information from them.
This is documented by Michael Kaplan on his blog (he was a developer of MSKLC) which I see you have linked to above.
When MSKLC encounters anything it does not understand, that portion is removed.
Extracting the layout using MSKLC will work for most keyboards, but there are a few–namely the Cherokee keyboard, and the Japanese & Korean keyboards (to name a few, I'm not sure how many more there are)–for which the extracted layout will NOT accurately or completely reflect the actual usage & features of the keyboard.
The Cherokee keyboard has chained dead keys which MSKLC doesn't support. And the far Eastern keyboards have modifier keys which MSKLC isn't aware of–that means entire layers/shift states which are missing!
Michael Kaplan supplies some code and unlocks some of the secrets of MSLKC and the accompanying software that can be used to get around some of these limitations but it requires a fair amount of doing things by hand–exactly what you're trying to avoid! Plus, Michael's objectives are aimed at creating keyboards with features that MSKLC can not create or understand, but which DO work in Windows (which is the opposite of what the OP is trying to accomplish).
I am sure that my solution comes too late to be of use to the OP, but perhaps it will be helpful in the future to someone in a similar situation. That is my hope and reason for posting this.
So far all I've done is explain that the other answers are insufficient. Even the best one will not and can not fully & accurately reproduce all of Windows' native keyboards and render them into KLC source files. This is really unfortunate and it is certainly not the fault of its author because that is a very clever piece of code/script! Thankfully the script & the source files (whose link may or may not still work) is useful & effective for the majority of Windows' keyboards, as well as any custom keyboards created by MSKLC.
The keyboards that have the advanced features that MSKLC doesn't support were created by the Windows DDK, but those features are not officially documented. Although one can learn quite a bit about their potential by studying the source files provided with MSKLC.
Sadly the only solution I can offer is 3rd party, paid software called KbdEdit. I believe it is the only currently available solution which is actually able to faithfully decode & recreate any of the Windows supplied keyboards–although there are a few advanced features which even it can not reproduce (such as keys combinations/hotkeys which perform special native language functions; for example: Ctrl+CapsLock to activate KanaLock (a Japanese modifier layer). KbdEdit DOES faithfully reproduce that modifier layer which MSKLC with strip away, it just doesn't support this alternate method of activating that shift state if you don't have a Japanese keyboard with a Kana lock key. Although, it will allow you to convert a key on your keyboard to a Kana key (perhaps Scroll Lock?).
Fortunately, none of those unsupported features are even applicable to an on-screen keyboard.
KbdEdit is a really powerful & amazing tool, and it has been worth every penny I paid for it! (And that's NOT something I would say about virtually any other paid software…)
Even though KbdEdit is 3rd party software, it is only needed to create the keyboards, not to use them. All of the keyboards it creates work natively on any Windows system without KbdEdit being installed.
It supports up to 15 modifier states and three addition modifier keys, one which is togglable–like CapsLock. It also supports chained dead keys, and remapping any of the keys on most any keyboard.
Why don't you use the on-screen keyboard (osk.exe)? Looks like you re-inventing the wheel. And not the easiest one!
I know where are these DLL files' path:
In your registry, you see:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts
where each branch has some value like "Layout File"="KBDSP.dll". The root directory is
C:\Windows\System32
and
C:\Windows\SystemWOW64
Those are all the keyboard layout files are located. For example, KBDUS.dll means "keyboard for US".
I tried to substitute the DLL file with my custom DLL made by MSKLC, and I found it loads the layout mapping images automatically in the "Language" - "input method" - "preview":
So we know that the mapping is there in the DLL.
Please check following Windows API
[DllImport("user32.dll")]
private static extern long LoadKeyboardLayout(string pwszKLID, uint Flags);
Check MSDN here
Related
I have added many voices using "Add language" under region and language. These appear under Text-to-speech in Speech. (I am using Windows 10)
I want to use these in my app with the SpeechSynthesizer class in System.Speech.Synthesis.
When listing the available voices in my application only a handful of those actually available are shown:
static void Main()
{
SpeechSynthesizer speech = new SpeechSynthesizer();
ReadOnlyCollection<InstalledVoice> voices = speech.GetInstalledVoices();
if (File.Exists("available_voices.txt"))
{
File.WriteAllText("available_voices.txt", string.Empty);
}
using (StreamWriter sw = File.AppendText("available_voices.txt"))
{
foreach (InstalledVoice voice in voices)
{
sw.WriteLine(voice.VoiceInfo.Name);
}
}
}
Looking in available_voices.txt only these voices are listed:
Microsoft David Desktop
Microsoft Hazel Desktop
Microsoft Zira Desktop
Microsoft Irina Desktop
But looking under Text-to-speech in the setttings there are many more, like Microsoft George and Microsoft Mark.
The accepted answer here:
SpeechSynthesizer doesn't get all installed voices
suggest changing the platform to x86. I tried this but i am not seeing any change.
This answer:
SpeechSynthesizer doesn't get all installed voices 2
suggest using .NET v4.5 because of a bug in System.Speech.Synthesis. I targeted .NET Framework 4.5 but i can still only retrieve 4 voices.
None of the answers in the questions i linked helped me solve my problem, so i am asking again. Any help is appretiated.
3 years passed after the original question was asked and API seems to contains the same issue, so here is a more "deep dive" answer.
TL;DR; Code example - at the bottom
The issue with the voice list is a weird design of the Microsoft Speech API - there are two sets of voices in Windows registered at different locations in registry - one is at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices, another one - at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices.
The problem is that SpeechSynthesizer's (or more specifically - VoiceSynthesis's) initialization routine is nailed to the first one, meanwhile we usually need a combination of both.
So, there are actually two ways to overcome the behavior.
Option 1 (the one mentioned throughout other answers): manipulate the registry to physically copy the voice definition records from Speech_OneCore registry which make them visible to SpeechSynthesizer. Here you have plenty of options: manual registry manipulation, PowerShell script, code-based etc.
Option 2 (the one I used in my project): use reflection to put additional voices into the internal VoiceSyntesis's _installedVoices field, effectively simulating what Microsoft did in their code.
Good news are that the Speech API source code is open now, so we don't have to fumble in darkness trying to understand what we need to do.
Here's the original code snippet:
using (ObjectTokenCategory category = ObjectTokenCategory.Create(SAPICategories.Voices))
{
if (category != null)
{
// Build a list with all the voicesInfo
foreach (ObjectToken voiceToken in category.FindMatchingTokens(null, null))
{
if (voiceToken != null && voiceToken.Attributes != null)
{
voices.Add(new InstalledVoice(voiceSynthesizer, new VoiceInfo(voiceToken)));
}
}
}
}
We just need to replace SAPICategories.Voices constant with another registry entry path and repeat the whole recipe.
Bad news are that all the needed classes, methods and fields used here are internal so we'll have to extensively use reflection to instantiate classes, call methods and get/set fields.
Please find below the example of my implementation - you call the InjectOneCoreVoices extension method on the synthesizer and it does the job. Note, that it throws exception if something goes wrong so don't forget proper try/catch surroundings.
public static class SpeechApiReflectionHelper
{
private const string PROP_VOICE_SYNTHESIZER = "VoiceSynthesizer";
private const string FIELD_INSTALLED_VOICES = "_installedVoices";
private const string ONE_CORE_VOICES_REGISTRY = #"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices";
private static readonly Type ObjectTokenCategoryType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Internal.ObjectTokens.ObjectTokenCategory")!;
private static readonly Type VoiceInfoType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Synthesis.VoiceInfo")!;
private static readonly Type InstalledVoiceType = typeof(SpeechSynthesizer).Assembly
.GetType("System.Speech.Synthesis.InstalledVoice")!;
public static void InjectOneCoreVoices(this SpeechSynthesizer synthesizer)
{
var voiceSynthesizer = GetProperty(synthesizer, PROP_VOICE_SYNTHESIZER);
if (voiceSynthesizer == null) throw new NotSupportedException($"Property not found: {PROP_VOICE_SYNTHESIZER}");
var installedVoices = GetField(voiceSynthesizer, FIELD_INSTALLED_VOICES) as IList;
if (installedVoices == null)
throw new NotSupportedException($"Field not found or null: {FIELD_INSTALLED_VOICES}");
if (ObjectTokenCategoryType
.GetMethod("Create", BindingFlags.Static | BindingFlags.NonPublic)?
.Invoke(null, new object?[] {ONE_CORE_VOICES_REGISTRY}) is not IDisposable otc)
throw new NotSupportedException($"Failed to call Create on {ObjectTokenCategoryType} instance");
using (otc)
{
if (ObjectTokenCategoryType
.GetMethod("FindMatchingTokens", BindingFlags.Instance | BindingFlags.NonPublic)?
.Invoke(otc, new object?[] {null, null}) is not IList tokens)
throw new NotSupportedException($"Failed to list matching tokens");
foreach (var token in tokens)
{
if (token == null || GetProperty(token, "Attributes") == null) continue;
var voiceInfo =
typeof(SpeechSynthesizer).Assembly
.CreateInstance(VoiceInfoType.FullName!, true,
BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {token}, null, null);
if (voiceInfo == null)
throw new NotSupportedException($"Failed to instantiate {VoiceInfoType}");
var installedVoice =
typeof(SpeechSynthesizer).Assembly
.CreateInstance(InstalledVoiceType.FullName!, true,
BindingFlags.Instance | BindingFlags.NonPublic, null,
new object[] {voiceSynthesizer, voiceInfo}, null, null);
if (installedVoice == null)
throw new NotSupportedException($"Failed to instantiate {InstalledVoiceType}");
installedVoices.Add(installedVoice);
}
}
}
private static object? GetProperty(object target, string propName)
{
return target.GetType().GetProperty(propName, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(target);
}
private static object? GetField(object target, string propName)
{
return target.GetType().GetField(propName, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(target);
}
}
After trying about all published solutions, I solved it by editing the registry:
copying Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Speech_OneCore\Voices\Tokens\MSTTS_V110_heIL_Asaf
(where MSTTS_V110_heIL_Asaf is the registry folder of the voice I want to use in .NET, but don't appear in GetInstalledVoices())
to a registry address that looks the same but instead of Speech_OneCore it is just Speech.
technically, to copy the registry folder, i exported the original folder, then edited the .reg file to change Speech OneCore to Speech, and then applied that new .reg file.
I solved it by installing voices from another source and getting Microsoft Speech Platform - Runtime (Version 11)
The available voices can be found on microsofts website (click on the red download button and the voices should be listed)
Sorry if my answer comes so late after the subject was posted but I developed a small tool which allows to patch the installed voices to make them available for the .NET text-to-speech engine.
The tool copies selected items from the "HKLM\SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens" key to "HKLM\SOFTWARE\Microsoft\Speech\Voices\Tokens".
If you're interested : TTSVoicePatcher (it's freeware, FR/EN)
Due to the manipulation of keys in HKLM, the tool requires administrator rights to be launched.
The Microsoft Speech Platform - Runtime Languages (Version 11) on the Microsoft website seem to contain only languages that are already installed. Not the ones that can be found under Speech_OneCore.
SCENARIO
I manage and organize many files during the day, the SendTo is the most used feature that I use on Windows.
PROBLEM
By default, when the user clicks an item/link of the contextmenu to send the files, the O.S does not show any kind of advise/notifier indicating that the files are copying to the selected destination.
I consider it a very wrong design issue because for big files its OK ...a progressbar will be shown, but if the files are to small it will not show any progressbar/visual indicator so is not possible to ensure that the files are copied (without manual effort) because I'm human and I could click outside the SendTo contextmenu by error.
So, I would like to develop a personal mini-tool that will help me to optimize my time showing me a notifier window wherever on the screen when I send/copy files using the SendTo feature from the contextmenu, and only the SendTo feature.
QUESTION
In just simple words, I want to detect a copy/send operation from SendTo menu to ensure that the click was done properly on the menu item (and not outside the menu), by also providing additional basic info such as the source folder, the destination folder, and the amount of files or the filepaths.
Any ideas to start developing this tool in the right direction?.
I will be grateful for a code example in C# or else VB.Net, preferably this last.
APPROACH
Since I don't know how to start doing this I mean which could be the easiest or the efficient way to intercept those SendTo calls, firstly I thought to hook the CopyFile or CopyFileEx API functions, but they do not provide the information that I need because that function will be called in any kind of copy operation and not only when I use the SendTo Feature, so I'm lost.
I'm not sure if I should investigate more about internal calls, or maybe investigate more about the windows contextmenu itself instead of messing with function hooks and ugly things that I could avoid.
My main idea is to develop a hidden WinForms (or else a windows service) that stays in background waiting for when I use the SendTo feature (when I click on an item of the SendTo menu) and then show any kind of visual indicator on the screen to ensure that I properly clicked that menu-item and maybe inform about the amount of files that I'm moving and where I'm moving them.
RESEARCH
Here is a code example that I think it demostrates how to instantiate the SendTo com object to create your own?, but its written in c++ and I'm not sure if the example is helpful because my intention is not to replace the SendTo menu but I'll keep this useful info here it it serves for something else:
How to add(enable) standard "Send To" context menu option in a namespace extension
The KNOWNFOLDERID constants docs gives some useful info about the SendTo folder, again I'm not sure if this could help maybe for a read/access monitoring approach?, I just keep the info here:
GUID: {8983036C-27C0-404B-8F08-102D10DCFD74}
Default Path: %APPDATA%\Microsoft\Windows\SendTo
Legacy Default Path: %USERPROFILE%\SendTo
In the Shell Extension Handlers docs there is a Copy hook handler which I don't know if it has relation with the SendTo's COM component and if that could help me in some way,
the same ignorance for IContextMenu::InvokeCommand method reference which maybe I could intercept it to identify a SendTo invocation?
By the moment I feel like flying blind.
I recently found this A managed "Send To" menu class but again its a example written in C/C++ (I think is the same source before) which I don't understand at all and again I'm not sure if that could help me because I repeat that replacing SendTo is not what I have in mind (just because I don't know how to properly do it avoiding all possible risks, I prefer to still let Windows logic copy/send the files, I just want to detect the copy operation to retrieve info)
EXPECTED RESULTS AND USAGE
Step 1:
Select a random file and use the SendTo menu (in my language, Spanish, the command name is 'Enviar a')
Step 2:
Let the .net application's logic (working in background) intercept the SendTo operation to retrieve info.
(I only need help with this step)
Step 3:
Display the info somewhere over the screen to ensure that the SendTo operation was performed, to ensure that I properly clicked the SendTo item (My Link).
(That popup is just a simulation, I don't know any way to retrieve all that info)
It's really simple to do once you understand what SendTo really does, and it doesn't involves COM or shell extensions at all. Basically, the send to menu is populated with the content of the SendTo folder of the user profile (C:\Users\\AppData\Roaming\Microsoft\Windows\SendTo by default in Windows 6.x).
When clicked, if the option is a shortcut to a folder it will copy the files there, but if there is a shortcut to a program (or a program executable itself) it will run that program, passing the paths of the selected files as command-line arguments.
From there, it's really trivial to make some program that simply takes paths as arguments, present some kind of notification and then copies the files or do whatever you want with them.
A quick and dirty example could be as follow (in C#, but could be done with anything else really):
private static void Main(string[] args)
{
if(MessageBox.Show("Are you sure you want to copy files?", "Copy files", MessageBoxButtons.YesNo) == DialogResult.No) return;
foreach (string file in args)
File.Copy(file, Path.Combine("c:\\temp", Path.GetFileName(file));
}
This just ask for confirmation for copying a bunch of files. Note that this really doesn't "intercepts" the send to menu, but rather handles it completely, so it's the program responsability to do any meaningful action. A more serious implementation could use the built-in Windows copy dialog and display some screen with progress or anything else, that's up to your needs.
It could also take some more parameters on the command line. When you place a shortcut in the SendTo folder, the destination could add some more parameters that will be passed as the first ones (before the file names). For example the destination of the shortcut can read c:\program files\copyfiles.exe c:\temp to pass the destination folder instead of hardcoding. The called program must then interpret the first parameter as the destination path and subsequent ones as the source files.
I've had to do something like this before. You don't even have to intercept the SendTo() function, you only need to make sure the the file has arrived. How about FileSystemWatcher if it's on the same computer?
You could use a watcher to watch before you send it, then, if the file successfully arrives at it's destination, you can display a successful message, and then kill the watcher.
Code Example
// Create a FileSystemWatcher property.
FileSystemWatcher fsw { get; set; }
// So we can set the FileToWatch within WatchFilesBeforeTransfer().
private string FileToWatch { get; set; }
private void WatchFilesBeforeTransfer(string FileName, string DestinationFolder)
{
fsw = new FileSystemWatcher();
fsw.Path = DestinationFolder;
FileToWatch = FileName;
// Only if you need support for multiple directories. Code example note included.
fsw.InclueSubdirectories = true;
// We'll be searching for the file name and directory.
fsw.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName
// If it's simply moving the file to another location on the computer.
fsw.Renamed += new RenamedEventHandler(FileRenamed);
// If it was copied, not moved or renamed.
fsw.Created += new FileSystemEventHandler(FileCreated);
fsw.EnableRaisingEvents = true;
}
// If the file is just renamed. (Move/Rename)
private void FileRenamed(Object source, RenamedEventArgs e)
{
// Do something.
// Note that the full filename is accessed by e.FullPath.
if (e.Name == FileToWatch)
{
DisplaySuccessfulMessage(e.Name);
KillFileWatcher();
}
}
// If creating a new file. (Copied)
private void FileCreated(Object source, FileSystemEventArgs e)
{
// Do something.
// Note that the full filename is accessed by e.FullPath.
if (e.Name == FileToWatch)
{
DisplaySuccessfulMessage(e.Name);
KillFileWatcher();
}
}
private void KillFileWatcher()
{
fsw.Dispose();
}
You can access the desired property information (like in your popup gif) in this way:
Folder name: Path.GetDirectory(e.FullPath); (like "C:\yo\")
Full file name: e.FullPath (like "C:\yo\hey.exe")
File name: e.Name (like "hey.exe")
Non-code execution process:
Before you initiate SendTo(), create an instance of the FileSystemWatcher property, and have it watch for a specific Folder/File name combination, which should show up in the watched folder: WatchFilesBeforeTransfer(FileName, DestinationFolder).
Initiate SendTo().
File received? DisplaySuccessfulSendToMessage(), KillFileWatcher();
???
Profit.
UPDATE:
I just realized that's just for one file. If you want to check for multiple files, you could either create multiple FileWatcher instances (not recommended), or use a List<string> object, like this:
private void SendTo(List<string> FileCollection)
{
// Clear your previous FileList.
FileList.Clear();
foreach (string file in FileCollection)
{
FileList.Add(file);
}
// Rest of the code.
}
List<string> FileList { get; set; }
private void WatchFilesBeforeTransfer(string DestinationFolder)
{
// Same code as before, but delete FileToWatch.
}
private void FileRenamed(Object source, RenamedEventArgs e)
{
foreach (string file in FileList)
{
if (e.Name == file)
{
// Do stuff.
}
}
}
private void FileCreated(Object source, FileSystemEventArgs e)
{
foreach (string file in FileList)
{
if (e.Name == file)
{
// Do stuff.
}
}
}
Hope this helps!
I'm afraid this ain't that easy.
I was playing around with the FileSystemWatcher on this, but with only partly success.
Something that should definitely work are File system drivers but this looks like just too, well, look at it...
In the end it might be the easiest way to write your own shell extension to access the SendTo folder, and use this instead of the SentTo command which would give you full control.
These might be some starters:
Windows shell extensions
Shell Context Menus
Maybe I come a little bit late to answer my own question, which was published on year 2015, but it wasn't until few days ago that I became interested in this matter again, with much more experience and knowledge gained in .NET after these years to start from scratch and try to understand everything that I did not understood in the past.
I just discovered that, as #Ňɏssa Pøngjǣrdenlarp already commented in the comments box, apparently the most viable way to accomplish this would be to implement my own SendTo context menu and use IFileOperationProgressSink interface to report progress, which for this firstly I need to depend on the usage of IFileOperation interface.
The reason to use the IFileOperation interface is because it seems the only way offered in the Windows API to let the developer perform multiple file operations (copy, move, rename, create or delete) all at once within the same progress dialog UI. This is probably the interface used by the system when the user selects multiple files or directories (via the SendTo menu or just CTRL+C and CTRL+V) to move or copy them at once, because it only shows one progress dialog with all the copy operations queued in it...
So clearly IFileOperation and IFileOperationProgressSink interfaces were what I need. But from what I know there is no managed implementation of these interfaces in the .NET framework namespaces (neither in the Microsoft's WindowsAPICodePack library), which seems somewhat inexcusable to me considering that these interfaces exists since the era of Windows VISTA or even earlier, and it is an indisputable improvement being the totally opposite of any built-in members that you can think of to perform copy, move, rename, create or delete operations, like for example System.IO.File.Copy or Microsoft.VisualBasic.FileIO.FileSystem.CopyFile method. All of them only supports a single operation and only on a single progress dialog at once.
So I focused to investigate on that suggested solution, and I ended up finding a good implementation of IFileOperation and IFileOperationProgressSink interfaces in this repository:
https://github.com/misterhaan/au.Shared/tree/master/IO/Files.FileOperation
And this one which is the original implementation that I found on a old Microsoft's article which also comes with a good PDF reading to learn some things:
https://github.com/mlaily/MSDNMagazine2007-.NET-Matters-IFileOperation-in-Windows-Vista
https://learn.microsoft.com/en-us/archive/msdn-magazine/2007/december/net-matters-ifileoperation-in-windows-vista
Actually, for what I've discussed in this thread to do since year 2015, delving into the use of the IFileOperationProgressSink interface to report progress would not be necessary (only if I really want a deep progress information), since it is enough to determine that the copy was started / the call to IFileOperation.PerformOperations function was performed, and if any problems occur during the copy then it will appear in the progress dialog UI.
But of course what is still necessary is to develop a shell-extension of a custom SendTo menu to replace the one built into Windows. Shell-extensions could be developed with SharpShell library.
I develop in VB.NET language. I managed to extend and document their IFileOperation implementation and the wrapper, and updated the enums adding the newer, missing values added for Windows 7 and Windows 8.
If it can be helpful for someone, I'm going to attach the IFIleOperation and the wrapper in Vb.NET all in a new answer (because it exceeds the maximum character limit allowed for a post).
Note that in my implementation of IFileOperation interface and the wrapper class with name FileSystemOperation you will find some return types and missing classes or methods (like HRESULT return type or NativeMethods class), I just can't share all in one place, but these missing things are things that any experienced programmer will know how to resolve (eg. change HRESULT to UInteger type, and go to Pinvoke.net to find any missing method from my NativeMethods class).
UPDATE
It seems that StackOverflow doesn't allow me to add another answer, so I'll upload my implementation on PasteBin instead:
Class FileSystemOperation
https://pastebin.com/nvgLWEXu
Interface IFileOperation
https://pastebin.com/GzammHtu
Interface IFileOperationProgressSink
https://pastebin.com/jf9JjzyH
Class ComObjectDisposer(Of T As Class)
https://pastebin.com/7mPeawWr
Enum TransferSourceFlags
https://pastebin.com/V7wSSEvv
Enum FileOperationFlags
https://pastebin.com/A223w9XY
That's all.
I'm performing some text-to-speech and I'd like to specify some special pronunciations in a lexicon file. I have ran MSDN's AddLexicon example verbatim, and it speaks the sentence but it does not use the given lexicon, something appears to be broken.
Here's the provided example:
using System;
using Microsoft.Speech.Synthesis;
namespace SampleSynthesis
{
class Program
{
static void Main(string[] args)
{
// Initialize a new instance of the SpeechSynthesizer.
using (SpeechSynthesizer synth = new SpeechSynthesizer())
{
// Configure the audio output.
synth.SetOutputToDefaultAudioDevice();
PromptBuilder builder = new PromptBuilder();
builder.AppendText("Gimme the whatchamacallit.");
// Append the lexicon file.
synth.AddLexicon(new Uri("c:\\test\\whatchamacallit.pls"), "application/pls+xml");
// Speak the prompt and play back the output file.
synth.Speak(builder);
}
Console.WriteLine();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
and lexicon file:
<lexicon version="1.0"
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon
http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"
alphabet="x-microsoft-ups" xml:lang="en-US">
<lexeme>
<grapheme> whatchamacallit </grapheme>
<phoneme> W S1 AX T CH AX M AX K S2 AA L IH T </phoneme>
</lexeme>
</lexicon>
The console opens, the text is spoken, but the new pronunciation isn't used. I have of course saved the file to c:\test\whatchamacallit.pls as specified.
I've tried variations of the Uri and file location (e.g. #"C:\Temp\whatchamacallit.pls", #"file:///c:\test\whatchamacallit.pls"), absolute and relative paths, copying it into the build folder, etc.
I ran Process Monitor and the file is not accessed. If it were a directory/file permission problem (which it isn't) I would still see the access denied messages, however I log no reference at all except the occasional one from my text editor. I do see the file accessed when I try File.OpenRead.
Unfortunately there are no error messages when using a garbage Uri.
On further investigation I realized this example is from Microsoft.Speech.Synthesis, whereas I'm using System.Speech.Synthesis over here. However from what I can tell they are identical except for some additional info and examples and both point to the same specification. Could this still be the problem?
I verified the project is set to use the proper .NET Framework 4.
I compared the example from MSDN to examples from the referenced spec, as well as trying those outright but it hasn't helped. Considering the file doesn't seem to be accessed I'm not surprised.
(I am able to use PromptBuilder.AppendTextWithPronunciation just fine but it's a poor alternative for my use case.)
Is the example on MSDN broken? How do I use a lexicon with SpeechSynthesizer?
After a lot of research and pitfalls I can assure you that your assumption is just plain wrong.
For some reason System.Speech.Synthesis.SpeechSynthesizer.AddLexicon() adds the lexicon to an internal list, but doesn't use it at all.
Seems like nobody tried using it before and this bug went unnoticed.
Microsoft.Speech.Synthesis.SpeechSynthesizer.AddLexicon() (which belongs to the Microsoft Speech SDK) on the other hand works as expected (it passes the lexicon on to the COM object which interprets it as advertised).
Please refer to this guide on how to install the SDK: http://msdn.microsoft.com/en-us/library/hh362873%28v=office.14%29.aspx
Notes:
people reported the 64-bit version to cause COM exceptions (because the library does not get installed correctly), I confirmed this on a 64bit Windows 7 machine
using the x86 version circumvents the problem
be sure to install the runtime before the SDK
be sure to also install a runtime language (as adviced on the linked page) as the SDK does not use the default system speech engine
You can use System.Speech.Synthesis.SpeechSynthesizer.SpeakSsml() instead of a lexicon.
This code changes pronunciation of "blue" to "yellow" and "dog" to "fish".
SpeechSynthesizer synth = new SpeechSynthesizer();
string text = "This is a blue dog";
Dictionary<string, string> phonemeDictionary = new Dictionary<string, string> { { "blue", "jelow" }, { "dog", "fyʃ" } };
foreach (var element in phonemeDictionary)
{
text = text.Replace(element.Key, "<phoneme ph=\"" + element.Value + "\">" + element.Key + "</phoneme>");
}
text = "<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"en-US\">" + text + "</speak>";
synth.SpeakSsml(text);
I've been looking into this a little recently on Windows 10.
There are two things I've discovered with System.Speech.Synthesis.
Any Voice you use, must be matched against the language in the Lexicon file.
Inside the lexicon you have the language:
<lexicon version="1.0"
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
alphabet="x-microsoft-ups" xml:lang="en-US">
I find that I can name my Lexicon as "blue.en-US.pls" and make a copy with "blue.en-GB.pls". Inside it will have xml:lang="en-GB"
In the code you'd use:
string langFile = Path.Combine(_appPath, $"blue.{synth.Voice.Culture.IetfLanguageTag}.pls");
synth.AddLexicon(new Uri(langFile), "application/pls+xml");
The other thing I discovered is, it doesn't work with "Microsoft Zira Desktop - English (United States)" at all. I don't know why.
This appears to be the default voice on Windows 10.
Access and change your default voice here:
%windir%\system32\speech\SpeechUX\SAPI.cpl
Otherwise you should be able to set it via code:
var voices = synth.GetInstalledVoices();
// US: David, Zira. UK: Hazel.
var voice = voices.First(v => v.VoiceInfo.Name.Contains("David"));
synth.SelectVoice(voice.VoiceInfo.Name);
I have David (United States) and Hazel (United Kingdom), and it works fine with either of those.
This appears to be directly related to whether the voice token in the registry has the SpLexicon key value. The Microsoft Zira Desktop voice does not have this registry value.
While Microsoft David Desktop voice has the following:
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\TTS_MS_EN-US_DAVID_11.0\Attributes\SpLexicon = {0655E396-25D0-11D3-9C26-00C04F8EF87C}
What am I doing:
My main intent is to enable user friendly text to speech for personal use on Win 7. Approach should work in Google Chrome, VS and Eclipse.
Code example:
Following code creates global keyboard hook for ctrl + alt + space, called hookEvent. If event fires, it starts/stops speaking clipboard contents ( that can be updated with ctrl + c ).
/// <summary>
/// KeyboardHook from: http://www.liensberger.it/web/blog/?p=207
/// </summary>
private readonly KeyboardHook hook = new KeyboardHook();
private readonly SpeechSynthesizer speaker = //
new SpeechSynthesizer { Rate = 3, Volume = 100 };
private void doSpeaking(string text)
{
// starts / stops speaking, while not blocking UI
if (speaker.State != SynthesizerState.Speaking)
speaker.SpeakAsync(text);
else
speaker.SpeakAsyncCancelAll();
}
private void hookEvent(object sender, KeyPressedEventArgs e)
{
this.doSpeaking(Convert.ToString(Clipboard.GetText()));
}
public Form1()
{
InitializeComponent();
hook.KeyPressed += new EventHandler<KeyPressedEventArgs>(hookEvent);
hook.RegisterHotKey(ModifierKeysx.Control|ModifierKeysx.Alt, Keys.Space);
}
Question:
I would prefer not using the clipboard. Or at least, restoring the value after, something like:
[MethodImpl(MethodImplOptions.Synchronized)]
private string getSelectedTextHACK()
{
object restorePoint = Clipboard.GetData(DataFormats.UnicodeText);
SendKeys.SendWait("^c");
string result = Convert.ToString(Clipboard.GetText());
Clipboard.SetData(DataFormats.UnicodeText, restorePoint);
return result;
}
What are my options?
Edit:
To my surprise, I found that my clipboard reader is the best way to go. I created a notification area app, that responds to left click (speaking clipboard) and right click (menu opens up). In menu the user can chance speed, speak or create a audio file.
MS provide accessibility tools that do cover what you're trying to do. If you take a look at documents about screen scraping. In short, every component is accessible in some manner, if you use some of the windows debugging tools you can get to see the component names/structures within. You can then use that, however, its complicated as most times you would need to be very specific for each application you intend to scrape from.
If you manage to scrape you dont need to use the clipboard, as you can access the text property of the apps direct. Its not something I've had to do, hence, Ive no code to offer off the top of my head, but the term "screen scraping" should point you in the right direction.
If to expand a little on what Bugfinder said, Microsoft provider a UI Automation Framework to solve problems like the one you mentioned:
In particular you can use the TextSelectionChangedEvent of TextPattern:
The problem with this solution is that it only works on supported operating systems and applications - and not all support this.
Your clipboard solution is acceptable for applications that do not provide a good automation interface.
But for many applications the UI Automation Framework will work well and will provide you with a far better solution.
I want to have a Folder browser in my application, but I don't want to use the FolderBrowserDialog. (For several reasons, such as it's painful to use)
I want to use the standard OpenFileDialog, but modified for the directories.
As an example, µTorrent has a nice implementation of it (Preferences/Directories/Put new downloads in:). The standard Open File Dialog enable the user to:
paste full paths in the text field at bottom
use "Favorite Links" bar on Vista
use Search on Vista
auto remember last directory
more...
Does anybody know how to implement this? In C#.
I am not sure about uTorrent but this sounds pretty much like new Vista's IFileDialog with FOS_PICKFOLDERS option set. Generic C# code for it would go something like:
var frm = (IFileDialog)(new FileOpenDialogRCW());
uint options;
frm.GetOptions(out options);
options |= FOS_PICKFOLDERS;
frm.SetOptions(options);
if (frm.Show(owner.Handle) == S_OK) {
IShellItem shellItem;
frm.GetResult(out shellItem);
IntPtr pszString;
shellItem.GetDisplayName(SIGDN_FILESYSPATH, out pszString);
this.Folder = Marshal.PtrToStringAuto(pszString);
}
Full code can be found here.
WindowsAPICodePack
var dlg = new Microsoft.WindowsAPICodePack.Dialogs.CommonOpenFileDialog();
dlg.IsFolderPicker = true;
See this answer by leetNightShade for a working solution.
There are three things I believe make this solution much better than all the others.
It is simple to use.
It only requires you include two files (which can be combined to one anyway) in your project.
It falls back to the standard FolderBrowserDialog when used on XP or older systems.
The author grants permission to use the code for any purpose you deem fit.
There’s no license as such as you are free to take and do with the code what you will.
Download the code here.