I'm trying to read out the TITLE & URL from the Microsoft EDGE Browser.
Doing this with System.Windows.Automation most preferably since the code base already uses this for other problems.
Is it possible with System.Windows.Automation?
How to access the URL?
I'm currently this far:
AutomationId "TitleBar"
ClassName "ApplicationFrameWindow"
Name = [string]
=> Reading out this element gives me the TITLE
=> Walking it's children, I find the item "addressEditBox":
AutomationId "addressEditBox"
ClassName "RichEditBox"
Name "Search or enter web address"
=> I always get back the string "Search or enter web address"
=> This is the control where the url is in, though it isn't updated as the user goes to a website, it always returns a fixed string.
In code:
var digger1 = AutomationElement.FromHandle(process.MainWindowHandle).RootElement.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach(AutomationElement d1 in digger1 {
if(d1.Current.ClassName.Equals("ApplicationFrameWindow")) {
var digger2 = d1.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach(AutomationElement d2 in digger2) {
if(d2.Current.ClassName.Equals("Windows.Ui.Core.CoreWindow")) {
var digger3 = d2.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach(AutomationElement d3 in digger3) {
if(d3.Current.AutomationId.Equals("addressEditBox")) {
var url = d3.Current.Name;
return url;
}
}
}
}
}
}
You're almost there. You just need to get the TextPattern from the addressEditBox element. Here is a full sample Console app that dumps out all currently running Edge's windows on the desktop:
class Program
{
static void Main(string[] args)
{
AutomationElement main = AutomationElement.FromHandle(GetDesktopWindow());
foreach(AutomationElement child in main.FindAll(TreeScope.Children, PropertyCondition.TrueCondition))
{
AutomationElement window = GetEdgeCommandsWindow(child);
if (window == null) // not edge
continue;
Console.WriteLine("title:" + GetEdgeTitle(child));
Console.WriteLine("url:" + GetEdgeUrl(window));
Console.WriteLine();
}
}
public static AutomationElement GetEdgeCommandsWindow(AutomationElement edgeWindow)
{
return edgeWindow.FindFirst(TreeScope.Children, new AndCondition(
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window),
new PropertyCondition(AutomationElement.NameProperty, "Microsoft Edge")));
}
public static string GetEdgeUrl(AutomationElement edgeCommandsWindow)
{
var adressEditBox = edgeCommandsWindow.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.AutomationIdProperty, "addressEditBox"));
return ((TextPattern)adressEditBox.GetCurrentPattern(TextPattern.Pattern)).DocumentRange.GetText(int.MaxValue);
}
public static string GetEdgeTitle(AutomationElement edgeWindow)
{
var adressEditBox = edgeWindow.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.AutomationIdProperty, "TitleBar"));
return adressEditBox.Current.Name;
}
[DllImport("user32")]
public static extern IntPtr GetDesktopWindow();
}
Related
Intro
I'm working on an application and I want to be able to change the language when the app is running. For cross-platform compatibility I'm using AvaloniaUI.
I've found a few helpful articles:
Simple localization in WPF
Simple localization in WPF, extended for multiple resource-files
Answer to question on StackOverflow (basically the first link)
The problem
On startup of the app a binding is created (in LocExtensionWithMultipleResxFiles) between my control on the view and string this[string key] ( in TranslationSourceWithMultipleResxFiles). The app correctly loads the translations on startup.
On my View I have a button, the ClickEvent correctly sets TranslationSourceWithMultipleResxFiles.Instance.CurrentCulture but the text in my view doesn't update. I'm not sure where I did something wrong or if I need to change the code somewhere, so any help is appreciated.
My code
Using the above articles I have the following code:
TranslationSourceWithMultipleResxFiles contains a Dictionary for all the ResourceManagers that are used. string this[string key] returns the translated text. CurrentCulture is the property you set to change the Culture.
public class TranslationSourceWithMultipleResxFiles : INotifyPropertyChanged
{
public static TranslationSourceWithMultipleResxFiles Instance { get; } = new TranslationSourceWithMultipleResxFiles();
private readonly Dictionary<string, ResourceManager> resourceManagerDictionary = new Dictionary<string, ResourceManager>();
// key is the baseName + stringName that is binded to, this returns the translated text.
public string this[string key]
{
get
{
var (baseName, stringName) = SplitName(key);
string? translation = null;
if (resourceManagerDictionary.ContainsKey(baseName))
translation = resourceManagerDictionary[baseName].GetString(stringName, currentCulture);
return translation ?? key;
}
}
// the culture TranslationSourceWithMultipleResxFiles uses for translations.
private CultureInfo currentCulture = CultureInfo.InstalledUICulture;
public CultureInfo CurrentCulture
{
get { return currentCulture; }
set
{
if (currentCulture != value)
{
currentCulture = value;
NotifyPropertyChanged(string.Empty); // string.Empty/null indicates that all properties have changed
}
}
}
// WPF bindings register PropertyChanged event if the object supports it and update themselves when it is raised
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public void AddResourceManager(ResourceManager resourceManager)
{
if (!resourceManagerDictionary.ContainsKey(resourceManager.BaseName))
resourceManagerDictionary.Add(resourceManager.BaseName, resourceManager);
}
public static (string baseName, string stringName) SplitName(string name)
{
int idx = name.LastIndexOf('.');
return (name.Substring(0, idx), name.Substring(idx + 1));
}
}
In xaml you set the Translation.ResourceManager per UserContorl/Window etc. This is used so multiple resource files can be used in the application. Each child Control looks to this ResourceManager for their translations.
public class Translation : AvaloniaObject
{
public static readonly AttachedProperty<ResourceManager> ResourceManagerProperty = AvaloniaProperty.RegisterAttached<Translation, AvaloniaObject, ResourceManager>("ResourceManager");
public static ResourceManager GetResourceManager(AvaloniaObject dependencyObject)
{
return (ResourceManager)dependencyObject.GetValue(ResourceManagerProperty);
}
public static void SetResourceManager(AvaloniaObject dependencyObject, ResourceManager value)
{
dependencyObject.SetValue(ResourceManagerProperty, value);
}
}
Creates a Binding between the Control on the view and the correct ResourceManager.
public class LocExtensionWithMultipleResxFiles : MarkupExtension
{
public string StringName { get; } // Key name of the translation in a resource file.
public LocExtensionWithMultipleResxFiles(string stringName)
{
StringName = stringName;
}
// Find out what ResourceManager this control uses
private ResourceManager? GetResourceManager(object control)
{
if (control is AvaloniaObject dependencyObject)
{
object localValue = dependencyObject.GetValue(Translation.ResourceManagerProperty);
if (localValue != AvaloniaProperty.UnsetValue)
{
if (localValue is ResourceManager resourceManager)
{
TranslationSourceWithMultipleResxFiles.Instance.AddResourceManager(resourceManager);
return resourceManager;
}
}
}
return null;
}
// Create a binding between the Control and the translated text in a resource file.
public override object ProvideValue(IServiceProvider serviceProvider)
{
object? targetObject = (serviceProvider as IProvideValueTarget)?.TargetObject;
if (targetObject?.GetType().Name == "SharedDp") // is extension used in a control template?
return targetObject; // required for template re-binding
string baseName = GetResourceManager(targetObject)?.BaseName ?? string.Empty; // if the targetObject has a ResourceManager set, BaseName is set
if (string.IsNullOrEmpty(baseName)) // if the targetobjest doesnt have a RM set, it gets the root elements RM.
{
// rootObject is the root control of the visual tree (the top parent of targetObject)
object? rootObject = (serviceProvider as IRootObjectProvider)?.RootObject;
baseName = GetResourceManager(rootObject)?.BaseName ?? string.Empty;
}
if (string.IsNullOrEmpty(baseName)) // template re-binding
{
if (targetObject is Control frameworkElement)
baseName = GetResourceManager(frameworkElement.TemplatedParent)?.BaseName ?? string.Empty;
}
// create a binding between the Control and the correct resource-file
var binding = new ReflectionBindingExtension
{
Mode = BindingMode.OneWay,
Path = $"[{baseName}.{StringName}]", // This is the ResourceManager.Key
Source = TranslationSourceWithMultipleResxFiles.Instance,
FallbackValue = "Fallback, can't set translation.",
TargetNullValue = StringName,
};
return binding.ProvideValue(serviceProvider);
}
}
My View
<Window <!-- Standard Window xaml -->
xmlns:l="clr-namespace:TestAppForMVVMwithBaseClasses.Localization"
l:Translation.ResourceManager="{x:Static p:Resources.ResourceManager}">
<StackPanel>
<TextBlock Text="{l:LocExtensionWithMultipleResxFiles String1}"/>
<Button Content="Nl" Click="CurrentCultureNl_Click"/>
<Button Content="En" Click="CurrentCultureEn_Click"/>
</StackPanel>
</Window>
I am able to record a video and save it with:
var doc = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
string filename = Path.GetRandomFileName () + ".mov";
var videopath = Path.Combine (doc, filename);
^ That's the video path I am saving it to but I can't find how to play the video from the file.
mp = new MPMoviePlayerController(NSUrl.FromFilename("Movies/Showhouzreel2music.m4v"));
^ I have tried this and can see this video file and play it, but this video was placed by me and won't work for the video I have taken.
I would love any directions or help you guys can give.
Thank you and if you need any more information, feel free to ask.
You said you can get MPMoviePlayerController working with a file you placed?
If so then just try this.
string newVideoPath = Path.GetFileName(videopath);
mp = new MPMoviePlayerController(NSUrl.FromFilename(newVideoPath));
EDIT (More elaborate answer):
Implement something like this.
public void PlayVideo(string videopath)
{
string name = Path.GetFileName(videopath);
Device.BeginInvokeOnMainThread(() =>
{
QLPreviewItemFileSystem prevItem = new QLPreviewItemFileSystem(name, videopath);
QLPreviewController previewController = new QLPreviewController();
previewController.DataSource = new PreviewControllerDS(prevItem);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(previewController, true, null);
});
}
public class PreviewControllerDS : QLPreviewControllerDataSource
{
private QLPreviewItem _item;
public PreviewControllerDS(QLPreviewItem item)
{
_item = item;
}
public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
{
return _item;
}
public override nint PreviewItemCount(QLPreviewController controller)
{
return 1;
}
}
public class QLPreviewItemFileSystem : QLPreviewItem
{
string _fileName, _filePath;
public QLPreviewItemFileSystem(string fileName, string filePath)
{
_fileName = fileName;
_filePath = filePath;
}
public override string ItemTitle
{
get
{
return _fileName;
}
}
public override NSUrl ItemUrl
{
get
{
return NSUrl.FromFilename(_filePath);
}
}
}
And whenever you need to call it use this:
PlayVideo(videopath);
This will also allow you to open up other files should you choose to in the future. (Like pdfs, images, etc.) Note: You might have to modify some code to get it to work, as I haven't tested this yet.
I ask because I'm noticing on my site, if I hit it with an iPhone, sometimes it shows the mobile views, sometimes it shows regular views.
I've also read that MVC 4's not particularly effective at determining if the browser is from a mobile device or not, is that true? If so, anything we can do about it?
Update: Microsoft have published a workaround package for this bug.
I have added this workaround at here,
public class MyDefaultViewLocationCache : DefaultViewLocationCache, IViewLocationCache
{
public MyDefaultViewLocationCache(TimeSpan timeSpan): base(timeSpan)
{
}
public MyDefaultViewLocationCache()
: base()
{
}
public new string GetViewLocation(HttpContextBase httpContext, string key)
{
var location = base.GetViewLocation(httpContext, key);
if (location == null)
{
var cache = httpContext.Cache;
RemoveAllCacheStartWith(key, cache);
}
return location;
}
private static void RemoveAllCacheStartWith(string key, System.Web.Caching.Cache cache)
{
var keyWithoutDisplayMode = key.Substring(0, key.Substring(0, key.Length - 1).LastIndexOf(':') + 1);
var items = new List<string>();
var enumerator = cache.GetEnumerator();
while (enumerator.MoveNext())
{
var _key = enumerator.Key.ToString();
if (_key.StartsWith(keyWithoutDisplayMode))
{
items.Add(_key);
}
}
foreach (string item in items)
{
cache.Remove(item);
}
}
public new void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath)
{
base.InsertViewLocation(httpContext, key, virtualPath);
}
}
// In App Start
ViewEngines.Engines.OfType<RazorViewEngine>().First().ViewLocationCache =
new MyDefaultViewLocationCache();
For detail see this.
I have almost exactly the same scenario described by Nathon Taylor in ASP.NET MVC - Sharing Session State Between Controllers. The problem is that if I save the path to the images inside a Session variable List<string> it is not being defined back in the ItemController so all the paths are being lost... Here's my setup:
Inside ImageController I have the Upload() action method:
public ActionResult Upload()
{
var newFile = System.Web.HttpContext.Current.Request.Files["Filedata"];
string guid = Guid.NewGuid() + newFile.FileName;
string itemImagesFolder = Server.MapPath(Url.Content("~/Content/ItemImages/"));
string fileName = itemImagesFolder + "originals/" + guid;
newFile.SaveAs(fileName);
var resizePath = itemImagesFolder + "temp/";
string finalPath;
foreach (var dim in _dimensions)
{
var resizedPath = _imageService.ResizeImage(fileName, resizePath, dim.Width + (dim.Width * 10/100), guid);
var bytes = _imageService.CropImage(resizedPath, dim.Width, dim.Height, 0, 0);
finalPath = itemImagesFolder + dim.Title + "/" + guid;
_imageService.SaveImage(bytes, finalPath);
}
AddToSession(guid);
var returnPath = Url.Content("~/Content/ItemImages/150x150/" + guid);
return Content(returnPath);
}
private void AddToSession(string fileName)
{
if(Session[SessionKeys.Images] == null)
{
var imageList = new List<string>();
Session[SessionKeys.Images] = imageList;
}
((List<string>)Session[SessionKeys.Images]).Add(fileName);
}
Then inside my ItemController I have the New() action method which has the following code:
List<string> imageNames;
var images = new List<Image>();
if (Session[SessionKeys.Images] != null) //always returns false
{
imageNames = Session[SessionKeys.Images] as List<string>;
int rank = 1;
foreach (var name in imageNames)
{
var img = new Image {Name = name, Rank = rank};
images.Add(img);
rank++;
}
}
Ok so why is this happening and how do I solve it?
Also, I was thinking of whether I could move the ActionMethod that takes care of the upload of the images into the ItemController and store the image paths inside a List property on the ItemController itself, would that actually work? Note though, that images are being uploaded and taken care of via an AJAX request. Then when the user submits the item entry form, all the data about the Item along with the images should be saved to the database...
Update:
I've updated the code. Also I think I should add that I'm using StructureMap as my controller factorory. Could it be a scoping issue? What is the default scope that is usually used by StructureMap?
public class StructureMapDependencyResolver : IDependencyResolver
{
public StructureMapDependencyResolver(IContainer container)
{
_container = container;
}
public object GetService(Type serviceType)
{
if (serviceType.IsAbstract || serviceType.IsInterface)
{
return _container.TryGetInstance(serviceType);
}
else
{
return _container.GetInstance(serviceType);
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _container.GetAllInstances<object>()
.Where(s => s.GetType() == serviceType);
}
private readonly IContainer _container;
}
And inside my Global.asax file:
private static IContainer ConfigureStructureMap()
{
ObjectFactory.Configure(x =>
{
x.For<IDatabaseFactory>().Use<EfDatabaseFactory>();
x.For<IUnitOfWork>().Use<UnitOfWork>();
x.For<IGenericMethodsRepository>().Use<GenericMethodsRepository>();
x.For<IUserService>().Use<UsersManager>();
x.For<IBiddingService>().Use<BiddingService>();
x.For<ISearchService>().Use<SearchService>();
x.For<IFaqService>().Use<FaqService>();
x.For<IItemsService>().Use<ItemsService>();
x.For<IMessagingService>().Use<MessagingService>();
x.For<IStaticQueriesService>().Use<StaticQueriesService>();
x.For < IImagesService<Image>>().Use<ImagesService>();
x.For<ICommentingService>().Use<CommentingService>();
x.For<ICategoryService>().Use<CategoryService>();
x.For<IHelper>().Use<Helper>();
x.For<HttpContext>().HttpContextScoped().Use(HttpContext.Current);
x.For(typeof(Validator<>)).Use(typeof(NullValidator<>));
x.For<Validator<Rating>>().Use<RatingValidator>();
x.For<Validator<TopLevelCategory>>().Use<TopLevelCategoryValidator>();
});
Func<Type, IValidator> validatorFactory = type =>
{
var valType = typeof(Validator<>).MakeGenericType(type);
return (IValidator)ObjectFactory.GetInstance(valType);
};
ObjectFactory.Configure(x => x.For<IValidationProvider>().Use(() => new ValidationProvider(validatorFactory)));
return ObjectFactory.Container;
}
Any thoughts?
I just added this to Global.asax.cs
protected void Session_Start()
{
}
It seems that this fixed the issue. I set a breakpoint that gets hit only once per session (as expected).
One possible reason for this is that the application domain restarts between the first and the second actions and because session is stored in memory it will be lost. This could happen if you recompile the application between the two. Try putting a breakpoints in the Application_Start and Session_Start callbacks in Global.asax and see if they are called twice.
Are you ever using it other than accessing HttpContext.Current directly in your code? In other words, are there any places where you're injecting the HttpContext for the sake of mocking in unit tests?
If you're only accessing it directly in your methods, then there's no reason to have the entry x.For<HttpContext>().HttpContextScoped().Use(HttpContext.Current); in you application startup. I wonder if it would start working if you removed it.
I cant seem to find the modifications I made to web.config in my FeatureRecievers Activated event. I try to get the modifications from the SpWebApplication.WebConfigModifications collection in the deactivate event, but this is always empty.... And the strangest thing is that my changes are still reverted after deactivating the feature...
My question is, should I not be able to view all changes made to the web.config files when accessing the SpWebApplication.WebConfigModifications collection in the Deactivating event? How should I go about to remove my changes explicitly?
public class FeatureReciever : SPFeatureReceiver
{
private const string FEATURE_NAME = "HelloWorld";
private class Modification
{
public string Name;
public string XPath;
public string Value;
public SPWebConfigModification.SPWebConfigModificationType ModificationType;
public bool createOnly;
public Modification(string name, string xPath, string value, SPWebConfigModification.SPWebConfigModificationType modificationType, bool createOnly)
{
Name = name;
XPath = xPath;
Value = value;
ModificationType = modificationType;
this.createOnly = createOnly;
}
}
private Modification[] modifications =
{
new Modification("connectionStrings", "configuration", "<connectionStrings/>", SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode, true),
new Modification("add[#name='ConnectionString'][#connectionString='Data Source=serverName;Initial Catalog=DBName;User Id=UserId;Password=Pass']", "configuration/connectionStrings", "<add name='ConnectionString' connectionString='Data Source=serverName;Initial Catalog=DBName;User Id=UserId;Password=Pass'/>", SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode, false)
};
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPSite siteCollection = (properties.Feature.Parent as SPWeb).Site as SPSite;
SPWebApplication webApplication = siteCollection.WebApplication;
siteCollection.RootWeb.Title = "Set from activating code at " + DateTime.Now.ToString();
foreach (Modification entry in modifications)
{
SPWebConfigModification webConfigModification = CreateModification(entry);
webApplication.WebConfigModifications.Add(webConfigModification);
}
webApplication.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
webApplication.WebService.Update();
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
SPSite siteCollection = (properties.Feature.Parent as SPWeb).Site as SPSite;
SPWebApplication webApplication = siteCollection.WebApplication;
siteCollection.RootWeb.Title = "Set from deactivating code at " + DateTime.Now.ToString();
IList<SPWebConfigModification> modifications = webApplication.WebConfigModifications;
foreach (SPWebConfigModification modification in modifications)
{
if (modification.Owner == FEATURE_NAME)
{
webApplication.WebConfigModifications.Remove(modification);
}
}
webApplication.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
webApplication.WebService.Update();
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
}
private SPWebConfigModification CreateModification(Modification entry)
{
SPWebConfigModification spWebConfigModification = new SPWebConfigModification()
{
Name = entry.Name,
Path = entry.XPath,
Owner = FEATURE_NAME,
Sequence = 0,
Type = entry.ModificationType,
Value = entry.Value
};
return spWebConfigModification;
}
}
Thanks for your time.
/Hans
Finally today I figured out what was wrong with my code (that is why the WebConfigModifications collection was empty when i queryied it in the deactivate event) it seems you must apply the changes in a different manner than I had done.
My original approach to applying the changes involved the following code:
Activate event
webApplication.Farm.Services.GetValue().ApplyWebConfigModifications();
webApplication.WebService.Update();
The "correct" way of doing it is this:
SPWebService.ContentService.ApplyWebConfigModifications();
webApplication.Update();
Although I am still at a loss why my original code did not work.. could someone with more knowlege of the configuration object in Sharepoint enlighten me?