WinRT Settings Popup implemented in base class isn't visible? - c#

I'm trying to make a Setting flyout easily accessible in all pages. I looked at the Settings example at http://code.msdn.microsoft.com/windowsapps/App-settings-sample-1f762f49/sourcecode?fileId=50851&pathId=2033699455.
However there are about a dozen functions and it's just dumb to have to copy/paste/maintain an undetermined number of copies of the same code in each page. So I took the code for the popup and put it in a base class that inherits from LayoutAwarePage base class. My pages then inherit from this new class. Ex:
// Base class declaration that includes settings flyout code
public class SettingsFlyoutAwarePage : myApp.Common.LayoutAwarePage
{
// Settings flyout code here
}
// Page declaration
public sealed partial class GroupedItemsPage : myApp.Common.SettingsFlyoutAwarePage
{
// Standard working page stuff here
}
Here is the actual code to create popup that IS being called and executed in the SettingsFlyoutAwarePage base class, which is identical to the sample code, but nothing actually appears on screen:
void onSettingsCommand(IUICommand command)
{
// Create a Popup window which will contain our flyout.
settingsPopup = new Popup();
settingsPopup.Closed += OnPopupClosed;
Window.Current.Activated += OnWindowActivated;
settingsPopup.IsLightDismissEnabled = true;
settingsPopup.Width = settingsWidth;
settingsPopup.Height = windowBounds.Height;
// Add the proper animation for the panel.
settingsPopup.ChildTransitions = new TransitionCollection();
settingsPopup.ChildTransitions.Add(new PaneThemeTransition()
{
Edge = (SettingsPane.Edge == SettingsEdgeLocation.Right) ?
EdgeTransitionLocation.Right :
EdgeTransitionLocation.Left
});
// Create a SettingsFlyout the same dimenssions as the Popup.
SettingsFlyout mypane = new SettingsFlyout();
mypane.Width = settingsWidth;
mypane.Height = windowBounds.Height;
// Place the SettingsFlyout inside our Popup window.
settingsPopup.Child = mypane;
// Let's define the location of our Popup.
settingsPopup.SetValue(Canvas.LeftProperty, SettingsPane.Edge == SettingsEdgeLocation.Right ? (windowBounds.Width - settingsWidth) : 0);
settingsPopup.SetValue(Canvas.TopProperty, 0);
settingsPopup.IsOpen = true;
}
While I can step through all the code and it all appears to execute just fine, I never actually see the flyout appear. Is it at least theoretically possible to do this from an inherited base class?

Did you implement the onCommandsRequested function as described in demo?
void onCommandsRequested(SettingsPane settingsPane, SettingsPaneCommandsRequestedEventArgs eventArgs)
{
UICommandInvokedHandler handler = new UICommandInvokedHandler(onSettingsCommand);
SettingsCommand generalCommand = new SettingsCommand("DefaultsId", "Defaults", handler);
eventArgs.Request.ApplicationCommands.Add(generalCommand);
}

If this helps anyone, here is a Settings flyout-aware class that you can inherit your XAML pages from. Of course you still have to implement the popup itself (demo code link)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.ApplicationSettings;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Navigation;
namespace MyApp.Common
{
[Windows.Foundation.Metadata.WebHostHidden]
public class MyAppBasePage : MyApp.Common.LayoutAwarePage
{
public MyAppBasePage()
{
}
private bool isSettingCharmEventRegistered;
// Used to determine the correct height to ensure our custom UI fills the screen.
private Rect windowBounds;
// Desired width for the settings UI. UI guidelines specify this should be 346 or 646 depending on your needs.
private double settingsWidth = 646;
// This is the container that will hold our custom content.
private Popup settingsPopup;
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Added to make sure the event handler for CommandsRequested is cleaned up before other scenarios.
if (this.isSettingCharmEventRegistered)
{
SettingsPane.GetForCurrentView().CommandsRequested -= onCommandsRequested;
this.isSettingCharmEventRegistered = false;
}
// Unregister the event that listens for events when the window size is updated.
Window.Current.SizeChanged -= OnWindowSizeChanged;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
windowBounds = Window.Current.Bounds;
// Added to listen for events when the window size is updated.
Window.Current.SizeChanged += OnWindowSizeChanged;
// Added to make sure the event handler for CommandsRequested is cleaned up before other scenarios.
if (!this.isSettingCharmEventRegistered)
{
SettingsPane.GetForCurrentView().CommandsRequested += onCommandsRequested;
this.isSettingCharmEventRegistered = true;
}
}
/// <summary>
/// Invoked when the window size is updated.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="e">Event data describing the conditions that led to the event.</param>
void OnWindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
windowBounds = Window.Current.Bounds;
}
/// <summary>
/// We use the window's activated event to force closing the Popup since a user maybe interacted with
/// something that didn't normally trigger an obvious dismiss.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="e">Event data describing the conditions that led to the event.</param>
private void OnWindowActivated(object sender, Windows.UI.Core.WindowActivatedEventArgs e)
{
if (e.WindowActivationState == Windows.UI.Core.CoreWindowActivationState.Deactivated)
{
settingsPopup.IsOpen = false;
}
}
/// <summary>
/// When the Popup closes we no longer need to monitor activation changes.
/// </summary>
/// <param name="sender">Instance that triggered the event.</param>
/// <param name="e">Event data describing the conditions that led to the event.</param>
void OnPopupClosed(object sender, object e)
{
Window.Current.Activated -= OnWindowActivated;
}
/// <summary>
/// This event is generated when the user opens the settings pane. During this event, append your
/// SettingsCommand objects to the available ApplicationCommands vector to make them available to the
/// SettingsPange UI.
/// </summary>
/// <param name="settingsPane">Instance that triggered the event.</param>
/// <param name="eventArgs">Event data describing the conditions that led to the event.</param>
void onCommandsRequested(SettingsPane settingsPane, SettingsPaneCommandsRequestedEventArgs eventArgs)
{
UICommandInvokedHandler handler = new UICommandInvokedHandler(onSettingsCommand);
SettingsCommand generalCommand = new SettingsCommand("DefaultsId", "Defaults", handler);
eventArgs.Request.ApplicationCommands.Add(generalCommand);
}
/// <summary>
/// This the event handler for the "Defaults" button added to the settings charm. This method
/// is responsible for creating the Popup window will use as the container for our settings Flyout.
/// The reason we use a Popup is that it gives us the "light dismiss" behavior that when a user clicks away
/// from our custom UI it just dismisses. This is a principle in the Settings experience and you see the
/// same behavior in other experiences like AppBar.
/// </summary>
/// <param name="command"></param>
void onSettingsCommand(IUICommand command)
{
// Create a Popup window which will contain our flyout.
settingsPopup = new Popup();
settingsPopup.Closed += OnPopupClosed;
Window.Current.Activated += OnWindowActivated;
settingsPopup.IsLightDismissEnabled = true;
settingsPopup.Width = settingsWidth;
settingsPopup.Height = windowBounds.Height;
// Add the proper animation for the panel.
settingsPopup.ChildTransitions = new TransitionCollection();
settingsPopup.ChildTransitions.Add(new PaneThemeTransition()
{
Edge = (SettingsPane.Edge == SettingsEdgeLocation.Right) ?
EdgeTransitionLocation.Right :
EdgeTransitionLocation.Left
});
// Create a SettingsFlyout the same dimenssions as the Popup.
SettingsFlyout mypane = new SettingsFlyout();
mypane.Width = settingsWidth;
mypane.Height = windowBounds.Height;
// Place the SettingsFlyout inside our Popup window.
settingsPopup.Child = mypane;
// Let's define the location of our Popup.
settingsPopup.SetValue(Canvas.LeftProperty, SettingsPane.Edge == SettingsEdgeLocation.Right ? (windowBounds.Width - settingsWidth) : 0);
settingsPopup.SetValue(Canvas.TopProperty, 0);
settingsPopup.IsOpen = true;
}
}
}

Related

SplitView and Back Button Navigation - UWP - C#

I am currently learning C# in UWP environment. I have a test app which is having a bit of the problem as described in Splitview with frame and navigating to another page, back button does not work.
But the my code is a little different from the above page.
My App.xaml.cs has the following code:
namespace Testing
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
//NavigationCacheMode.Enabled;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
//--------------------------------------------------------------------
//Window.Current.Content = new SplitShellPage(rootFrame);
//--------------------------------------------------------------------
Window.Current.Content = rootFrame;
}
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested += App_BackRequested;
// Ensure the current window is active
Window.Current.Activate();
}
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
private void App_BackRequested(object sender, Windows.UI.Core.BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
return;
// Navigate back if possible, and if the event has not
// already been handled .
if (rootFrame.CanGoBack && e.Handled == false)
{
e.Handled = true;
rootFrame.GoBack();
}
}
}
}
If I use Window.Current.Content = new SplitShellPage(rootFrame); as in place of Window.Current.Content = rootFrame; the SplitView works but the back button does not work. If I use the second line then SplitView doesn't work but back navigation works.
I even tried setting my launch page to SplitView page as rootFrame.Navigate(typeof(SplitShellPage), e.Arguments); but that causes the application to stop at runtime and the application doesn't start.
The other pages with code are as:
SplitShellPage.xaml.cs
namespace Testing.Pages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SplitShellPage : Page
{
public SplitShellPage(Frame frame)
{
this.InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
shell_splitview.Content = frame;
(shell_splitview.Content as Frame).Navigate(typeof(MainPage));
}
private void hamburger_btn_Click(object sender, RoutedEventArgs e)
{
shell_splitview.IsPaneOpen = !shell_splitview.IsPaneOpen;
}
}
}
This method of work I found when searching on google to make the NavigationPane work in all the pages and it does work in all the pages just without the Back Navigation.
MainPage.xaml.cs
namespace Testing
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
}
private void Settings_Flyout_Click(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(SettingsPage));
}
}
}
I have done some searches on google but nothing makes sense because they use some other methods and things get more complicated.
Please let me know what I am doing wrong here and how to make it right. Thanks.
P.S. Let me know if I need to also share the XAML files.
If I use Window.Current.Content = new SplitShellPage(rootFrame); as in place of Window.Current.Content = rootFrame; the SplitView works but the back button does not work
In this situation, the frame currently you are using is shell_splitview.Content.
Because you are using this code for navigating (shell_splitview.Content as Frame).Navigate(typeof(MainPage));. So in App.xaml.cs the method App_BackRequested method try to get the Window.Current.Content as Frame for navigating, the back button will not work.
The solution is provided by the demo in the thread you reference, to add the BackRequested event handle in the page which defined the SplitView. Update your code in SplitShellPage.xaml.cs as follows the back button will work.
public SplitShellPage(Frame frame)
{
this.InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
shell_splitview.Content = frame;
(shell_splitview.Content as Frame).Navigate(typeof(MainPage));
SystemNavigationManager.GetForCurrentView().BackRequested += SplitShellPage_BackRequested;
}
private void SplitShellPage_BackRequested(object sender, BackRequestedEventArgs e)
{
Frame myFrame = shell_splitview.Content as Frame;
if (myFrame.CanGoBack)
{
e.Handled = true;
myFrame.GoBack();
}
}
If I use the second line then SplitView doesn't work but back navigation works.
In this situation, we even did not access the SplitShellPage, and didn't access the SplitView. It is just a implemention of back button between two simple page. It works and has noting relationship with the content of SplitView.
The key point for this issue is to clear which frame you are using for navigating now , rootFrame(Window.Current.Content) or the SplitViewContent frame. And navigating back with back button need the same frame.
You can download the demo from the thread you referenced for further testing. And more details please reference Back button navigation.

C# Side Scrolling on Touch Screen with JavaScript Site

We have a C# Container, a Windows.Form.WebBrowser Class is the called, on a website that has no vertical scrolling (only horizontal).
Scrolling works great in Internet Explorer, Chrome, and Firefox with the touch screen, however when the website is launched inside the Windows.Form.WebBrowser the touch scrolling does not work. It registers the touch/drag event like a left click and drag without grabbing the website.
It will also select the text on the website below.
We created a sample site to demonstrate the issue:
Test website with side scroll. On a touch screen drag and scroll works
namespace WindowsFormsApplication1
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.webBrowser1 = new System.Windows.Forms.WebBrowser();
this.SuspendLayout();
//
// webBrowser1
//
this.webBrowser1.Dock = System.Windows.Forms.DockStyle.Fill;
this.webBrowser1.Location = new System.Drawing.Point(0, 0);
this.webBrowser1.MinimumSize = new System.Drawing.Size(20, 20);
this.webBrowser1.Name = "webBrowser1";
this.webBrowser1.ScrollBarsEnabled = false;
this.webBrowser1.Size = new System.Drawing.Size(2419, 1088);
this.webBrowser1.TabIndex = 0;
this.webBrowser1.Url = new System.Uri("https://beta.slimcrm.com/sideScrollTest.html", System.UriKind.Absolute);
this.webBrowser1.AllowNavigation = false;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(16F, 31F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(2419, 1088);
this.Controls.Add(this.webBrowser1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.WebBrowser webBrowser1;
}
}
We have 2 developers who have searched for 2 days through this site and the internet but nothing we try seems to allow the C# Windows.Form.WebBrowser to register the touch events and drag the website to the left.
We believe it has to be tied to the C#, because the drag events on the touch screen work great in all web browsers. Framework for the site is Knockout.js, Restful API, all within a C# Container.

Windows store app: Changing Background image Dynamically from code

Im working on a weather app and i want to change the background image dynamically depending on weather its sunny, cloudy and so on. I set the Background image for all the pages from App.xaml.cs as following:
rootFrame.Background = new ImageBrush
{
Stretch = Windows.UI.Xaml.Media.Stretch.UniformToFill,
ImageSource = new BitmapImage { UriSource = new Uri("ms-appx:///Assets/blueSky.jpg") }
};
I set the image in App.xaml.cs so all the pages gets the same image, and i dont need to load the image everytime. Full code of App.xaml.cs:
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
// Her we set the application background Image for all pages
rootFrame.Background = new ImageBrush
{
Stretch = Windows.UI.Xaml.Media.Stretch.UniformToFill,
ImageSource = new BitmapImage { UriSource = new Uri("ms-appx:///Assets/blueSky.jpg") }
};
weather.Common.SuspensionManager.RegisterFrame(rootFrame, "appFrame");
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
await weather.Common.SuspensionManager.RestoreAsync();
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
if (!rootFrame.Navigate(typeof(MainPage), args.Arguments))
{
throw new Exception("Failed to create initial page");
}
}
// Ensure the current window is active
Window.Current.Activate();
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private async void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
await weather.Common.SuspensionManager.SaveAsync();
deferral.Complete();
}
}
This works fine for 1 image, but i cant auto change the image. Any ideas?
Where is the code where you get the weather from a web service? In that code, get the current weather and then, based on the forecast, set the background picture.

Avoid Error too many changes at once in directory

how to avoid the error of FileSystemWatcher in C#?
too many changes at once in directory
I have to detect all changes on a network share.
The InternalBufferSize is increased to 8192 * 128
There are two things you should do:
Set InternalBufferSize to the maximum supported value (65536). Your attempt to set it to "8192 * 128" is larger than the maximum supported value listed in the documentation, so you may not have increased the buffer size at all.
Queue events from the FileSystemWatcher onto a background thread for processing.
It's the second point here that isn't well understood, and really should be documented on MSDN. Internally, FileSystemWatcher is queuing change events into that internal buffer you set the size of above. Critically however, items are only removed from that buffer after your event handler returns. This means every cycle of overhead your event handlers introduce increases the possibility of the buffer filling up. What you should do is clear the limited queue of the FileSystemWatcher as quickly as possible, and move the events into your own infinite queue, to process at the rate you can handle, or discard if you care to do so, but with some intelligence around it.
Here's basically what I do in my code. First, I start my own dispatcher thread:
Dispatcher changeDispatcher = null;
ManualResetEvent changeDispatcherStarted = new ManualResetEvent(false);
Action changeThreadHandler = () =>
{
changeDispatcher = Dispatcher.CurrentDispatcher;
changeDispatcherStarted.Set();
Dispatcher.Run();
};
new Thread(() => changeThreadHandler()) { IsBackground = true }.Start();
changeDispatcherStarted.WaitOne();
Then I create the watcher. Note the buffer size being set. In my case, I only watch changes in the target directory, not subdirectories:
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.InternalBufferSize = 64 * 1024;
watcher.IncludeSubdirectories = false;
Now I attach my event handlers, but here I invoke them onto my dispatcher rather than running them synchronously in the watcher thread. Yes, the events will be processed in order by the dispatcher:
watcher.Changed += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnChanged(sender, e)));
watcher.Created += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnCreated(sender, e)));
watcher.Deleted += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnDeleted(sender, e)));
watcher.Renamed += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnRenamed(sender, e)));
And finally, after disposing of the FileSystemWatcher (you were doing that, right?), you need to shut down your dispatcher:
watcher.Dispose()
changeDispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
And that's it. I was getting this problem myself, both in network and local scenarios. After using this approach, I wasn't able to generate this error again, even when hammering out empty files to watched directories as fast as possible. If you did ever manage to somehow exhaust the buffer in this case (which I'm not sure is possible, the API upstream is probably slower), there's still further room for optimization here. As long as your dispatcher is over the "tipping point" though, where the sender can't post the events faster than you can dispatch them, you'll never get a backlog, and hence never blow the buffer. I believe this approach puts you into that safe area.
I think I might have found a pattern that can help improve the the usage of the buffer considerably.
The issue with this class is that until the delegates for the events finish running, it cannot release the memory it uses to hold that information.
For the life of me I don't know why the maximum InternalBufferSize was set to 64Kb, but with this idea you'll use that small buffer much more effectively.
If instead of doing the operations inside the delegates you simply queue them and defer their execution for a background worker, the amount of memory used by it will be considerably smaller.
In fact, I've got no idea why the class in itself doesn't implement something like this in the first place.
The code below is just a sample piece of code for the idea, and shouldn't be used in production environments, but it increased the number of files I could copy and keep track of drastically.
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.IO;
using System.Threading;
using NUnit.Framework;
namespace Soundnet.Synchronisation.FileSystemWatcherTests
{
/// <summary>
///
/// </summary>
[TestFixture]
public class Tests
{
static readonly ConcurrentQueue<Change> ChangesQueue = new ConcurrentQueue<Change>();
const string Destination = #"c:\Destination";
const string Source = #"c:\Source";
/// <summary>
/// Tests this instance.
/// </summary>
[Test]
public void Test()
{
var changesBackgroundWorker = new BackgroundWorker();
changesBackgroundWorker.DoWork += ChangesBackgroundWorkerOnDoWork;
changesBackgroundWorker.RunWorkerAsync();
var fileSystemWatcher = new FileSystemWatcher
{
Path = Source,
EnableRaisingEvents = true,
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.CreationTime | NotifyFilters.FileName | NotifyFilters.DirectoryName,
InternalBufferSize = 65536
};
fileSystemWatcher.Created += FileSystemWatcherOnCreated;
fileSystemWatcher.Deleted += FileSystemWatcherOnDeleted;
fileSystemWatcher.Renamed += FileSystemWatcherOnRenamed;
fileSystemWatcher.Error += FileSystemWatcherOnError;
while (true)
Thread.Sleep(1000000);
}
/// <summary>
/// Changeses the background worker configuration document work.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="doWorkEventArgs">The <see cref="DoWorkEventArgs"/> instance containing the event data.</param>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
private static void ChangesBackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
while (true)
{
Change change;
if (ChangesQueue.TryDequeue(out change))
{
var backgroundWorker = new BackgroundWorker();
switch (change.ChangeType)
{
case WatcherChangeTypes.Created:
backgroundWorker.DoWork += (o, args) =>
{
var fileSystemType = GetFileSystemType(change.FullPath);
var newItem = Path.Combine(Destination, change.Name);
while (true)
{
try
{
switch (fileSystemType)
{
case FileSystemType.File:
File.Copy(change.FullPath, newItem, true);
break;
case FileSystemType.Directory:
var directorySecurity =
Directory.GetAccessControl(change.FullPath);
Directory.CreateDirectory(newItem, directorySecurity);
break;
case FileSystemType.NotExistant:
break;
}
return;
}
catch (IOException exception)
{
Thread.Sleep(100);
Console.WriteLine(exception.Message);
}
}
};
break;
case WatcherChangeTypes.Deleted:
backgroundWorker.DoWork += (o, args) =>
{
var itemToDelete = Path.Combine(Destination, change.Name);
var fileSystemType = GetFileSystemType(itemToDelete);
switch (fileSystemType)
{
case FileSystemType.File:
File.Delete(itemToDelete);
break;
case FileSystemType.Directory:
Directory.Delete(itemToDelete, true);
break;
}
};
break;
case WatcherChangeTypes.Changed:
backgroundWorker.DoWork += (o, args) =>
{
var fileSystemType = GetFileSystemType(change.FullPath);
var newItem = Path.Combine(Destination, change.Name);
switch (fileSystemType)
{
case FileSystemType.File:
File.Copy(change.FullPath, newItem, true);
break;
}
};
break;
case WatcherChangeTypes.Renamed:
backgroundWorker.DoWork += (o, args) =>
{
var fileSystemType = GetFileSystemType(change.FullPath);
var oldItem = Path.Combine(Destination, change.OldName);
var newItem = Path.Combine(Destination, change.Name);
switch (fileSystemType)
{
case FileSystemType.File:
if (File.Exists(oldItem))
File.Move(oldItem, newItem);
break;
case FileSystemType.Directory:
if (Directory.Exists(oldItem))
Directory.Move(oldItem, newItem);
break;
}
};
break;
case WatcherChangeTypes.All:
break;
default:
throw new ArgumentOutOfRangeException();
}
backgroundWorker.RunWorkerAsync();
}
}
}
/// <summary>
/// Files the system watcher configuration created.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="fileSystemEventArgs">The <see cref="FileSystemEventArgs"/> instance containing the event data.</param>
private static void FileSystemWatcherOnCreated(object sender, FileSystemEventArgs fileSystemEventArgs)
{
ChangesQueue.Enqueue(new Change
{
ChangeType = WatcherChangeTypes.Created,
FullPath = fileSystemEventArgs.FullPath,
Name = fileSystemEventArgs.Name
});
}
/// <summary>
/// Files the system watcher configuration deleted.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="fileSystemEventArgs">The <see cref="FileSystemEventArgs"/> instance containing the event data.</param>
private static void FileSystemWatcherOnDeleted(object sender, FileSystemEventArgs fileSystemEventArgs)
{
ChangesQueue.Enqueue(new Change
{
ChangeType = WatcherChangeTypes.Deleted,
FullPath = fileSystemEventArgs.FullPath,
Name = fileSystemEventArgs.Name
});
}
/// <summary>
/// Files the system watcher configuration error.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="errorEventArgs">The <see cref="ErrorEventArgs"/> instance containing the event data.</param>
private static void FileSystemWatcherOnError(object sender, ErrorEventArgs errorEventArgs)
{
var exception = errorEventArgs.GetException();
Console.WriteLine(exception.Message);
}
/// <summary>
/// Files the system watcher configuration renamed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="fileSystemEventArgs">The <see cref="RenamedEventArgs"/> instance containing the event data.</param>
private static void FileSystemWatcherOnRenamed(object sender, RenamedEventArgs fileSystemEventArgs)
{
ChangesQueue.Enqueue(new Change
{
ChangeType = WatcherChangeTypes.Renamed,
FullPath = fileSystemEventArgs.FullPath,
Name = fileSystemEventArgs.Name,
OldFullPath = fileSystemEventArgs.OldFullPath,
OldName = fileSystemEventArgs.OldName
});
}
/// <summary>
/// Gets the type of the file system.
/// </summary>
/// <param name="fullPath">The full path.</param>
/// <returns></returns>
private static FileSystemType GetFileSystemType(string fullPath)
{
if (Directory.Exists(fullPath))
return FileSystemType.Directory;
if (File.Exists(fullPath))
return FileSystemType.File;
return FileSystemType.NotExistant;
}
}
/// <summary>
/// Type of file system object
/// </summary>
internal enum FileSystemType
{
/// <summary>
/// The file
/// </summary>
File,
/// <summary>
/// The directory
/// </summary>
Directory,
/// <summary>
/// The not existant
/// </summary>
NotExistant
}
/// <summary>
/// Change information
/// </summary>
public class Change
{
/// <summary>
/// Gets or sets the type of the change.
/// </summary>
/// <value>
/// The type of the change.
/// </value>
public WatcherChangeTypes ChangeType { get; set; }
/// <summary>
/// Gets or sets the full path.
/// </summary>
/// <value>
/// The full path.
/// </value>
public string FullPath { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the old full path.
/// </summary>
/// <value>
/// The old full path.
/// </value>
public string OldFullPath { get; set; }
/// <summary>
/// Gets or sets the old name.
/// </summary>
/// <value>
/// The old name.
/// </value>
public string OldName { get; set; }
}
}
From MSDN;
The Windows operating system notifies your component of file changes
in a buffer created by the FileSystemWatcher. If there are many
changes in a short time, the buffer can overflow. This causes the
component to lose track of changes in the directory, and it will only
provide blanket notification. Increasing the size of the buffer with
the InternalBufferSize property is expensive, as it comes from
non-paged memory that cannot be swapped out to disk, so keep the
buffer as small yet large enough to not miss any file change events.
To avoid a buffer overflow, use the NotifyFilter and
IncludeSubdirectories properties so you can filter out unwanted change
notifications.
I have the experience that FileSystemWatcher is not always the most reliable thing to use. You could specify the filter to narrow down the files you are watching (NotifyFilter), or increase the buffer size.
But depending on your requirements you may also want to do it another way, like polling every x seconds to get the list of files. But then you might need to tell us more about your business case.
SHChangeNotifyRegister can be used to get shell notifications.
It should be fixed if you increase the buffer size but its not a practical solution.
Because to make sure it always records everything you would need to make the buffer huge.
And that will effect the performance greatlly.
And i think performance issues could be fixed by implementing multi threading.

Registry Watcher C#

I'm a newbie to WMI and I need to implement RegistryValueChangeEvent in a C# service.
I need an event handler that gets triggered each time any one of a set of registry values is changed. I want behavior similar to the FileSystemWatcher class's Changed event, but for registry values.
If there's some other technique I could use to accomplish the same task, I'd appreciate that as well. My minimum requirement is that it be a better solution than what I have now: polling every 20 seconds and comparing the registry value with the last result.
Please provide example code in your answer. If I can get an example for watching just one registry value, that would be fine.
I need a solution in .Net 2.0
Thanks.
WMI can sometimes be interesting to work with...I think I understand your question, so take a look at the code snippet below and let me know if it's what you're looking for.
// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="Program.cs" company="">
//
// </copyright>
// <summary>
// Defines the WmiChangeEventTester type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------
namespace WmiExample
{
using System;
using System.Management;
/// <summary>
/// </summary>
public class WmiChangeEventTester
{
/// <summary>
/// Initializes a new instance of the <see cref="WmiChangeEventTester"/> class.
/// </summary>
public WmiChangeEventTester()
{
try
{
// Your query goes below; "KeyPath" is the key in the registry that you
// want to monitor for changes. Make sure you escape the \ character.
WqlEventQuery query = new WqlEventQuery(
"SELECT * FROM RegistryValueChangeEvent WHERE " +
"Hive = 'HKEY_LOCAL_MACHINE'" +
#"AND KeyPath = 'SOFTWARE\\Microsoft\\.NETFramework' AND ValueName='InstallRoot'");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
Console.WriteLine("Waiting for an event...");
// Set up the delegate that will handle the change event.
watcher.EventArrived += new EventArrivedEventHandler(HandleEvent);
// Start listening for events.
watcher.Start();
// Do something while waiting for events. In your application,
// this would just be continuing business as usual.
System.Threading.Thread.Sleep(100000000);
// Stop listening for events.
watcher.Stop();
}
catch (ManagementException managementException)
{
Console.WriteLine("An error occurred: " + managementException.Message);
}
}
/// <summary>
/// </summary>
/// <param name="sender">
/// The sender.
/// </param>
/// <param name="e">
/// The e.
/// </param>
private void HandleEvent(object sender, EventArrivedEventArgs e)
{
Console.WriteLine("Received an event.");
// RegistryKeyChangeEvent occurs here; do something.
}
/// <summary>
/// </summary>
public static void Main()
{
// Just calls the class above to check for events...
WmiChangeEventTester receiveEvent = new WmiChangeEventTester();
}
}
}
You really don't need WMI, as others have pointed out. I too have used RegistryMonitor with no problems.
If you need an example, there's already example code for the RegistryMonitor on the page itself. Did you scroll down to this bit on the code project:
public class MonitorSample
{
static void Main()
{
RegistryMonitor monitor = new
RegistryMonitor(RegistryHive.CurrentUser, "Environment");
monitor.RegChanged += new EventHandler(OnRegChanged);
monitor.Start();
while(true);
monitor.Stop();
}
private void OnRegChanged(object sender, EventArgs e)
{
Console.WriteLine("registry key has changed");
}
}
Are you limited to WMI?
If not you can use RegNotifyChangeKeyValue wrappers like RegistryMonitor
You'll need to utilize WMI for it. See http://msdn.microsoft.com/en-us/library/aa393035.aspx

Categories

Resources