Execute Pick Point from Windows Form in ArcGIS - arcobjects

I have a windows form which is launched with an ESRI AddIn button (ArcGIS 10.2 and Windows 7). On my form I have a button to pick a point from the Map. I have added an ESRI BaseTool class to the project, which has an OnMouseDown event.
The problem is that I cannot get the Tool to run. Note that the tool is not on the ArcGIS Command Bar (like the button is) but the tool is still found by the Find(uid) process.
When the Tool was added to the project (using the ArcGIS Add BaseTool process) it didn't update the .esriaddinx file. I had to do that manually.
My Addin file is:
<AddIn language="CLR4.0" library="HVLR_Processing.dll" namespace="HVLR_Processing">
<ArcMap>
<Commands>
<Button id="RMS_HVLR_Processing_clsHVLR_Processing" class="clsHVLR_Processing" ...
<Tool id="HVLR_PickTool" class="clsMapPick" category="Add-In Controls" caption="" message="" tip="" image="" />
</Commands>
</ArcMap>
The clsMapClick code contains the OnMouseDown event.
To start the process I have tried many methods. I can retrieve the Tool but when I execute it (or assign it to the CurrentTool) nothing happens.
UID pUID;
ICommandItem pCmdItem;
ICommand pCmd;
clsMapPick pPick;
ITool pTool;
try
{
this.WindowState = FormWindowState.Minimized;
m_pApp.CurrentTool = null;
pUID = new UIDClass();
pUID.Value = "HVLR_PickTool";
pCmdItem = m_pApp.Document.CommandBars.Find(pUID, false, false);
if (pCmdItem != null)
{
m_pApp.CurrentTool = pCmdItem; // Nothing happens
m_pApp.CurrentTool.Execute(); // Nothing happens
m_pApp.CurrentTool.Refresh();
}
}
catch (Exception ex)
Can anyone tell me how to get this tool to execute?

OK. Big stuff-up. You can't add a BaseTool to an ESRI AddIn; it's a COM object. What has to be done is:
Create a new ESRI Tool class.
Add a boolean variable to the class to indicate the mousedown event has fired.
In the OnUpdate method put some code to continue until the mousedown event has fired.
Create an OnMouseDown event handler by starting to type protected void On... and itellisense will allow you to select the event you want to track.
Put the code you want to run in the OnMouseDown event handler and also set the boolean value to true.
Code:
public class clsMapPick : ESRI.ArcGIS.Desktop.AddIns.Tool
{
private bool m_bIsFinished = false;
private int m_iXPixel = -1;
private int m_iYPixel = -1;
//private string m_sError = "";
//private bool m_bSuccess = true;
public clsMapPick()
{
}
protected override void OnActivate()
{
base.OnActivate();
return;
}
protected override void OnUpdate()
{
if (m_bIsFinished)
{
m_bIsFinished = false;
frmHVLR.m_dX = m_iXPixel;
frmHVLR.m_dX = m_iYPixel;
}
}
protected override void OnMouseDown(MouseEventArgs arg)
{
base.OnMouseDown(arg);
m_iXPixel = arg.X;
m_iYPixel = arg.Y;
m_bIsFinished = true;
}
}
In the form where the button for clicking on the map is fired:
string sError = "";
dPickedX = 0;
dPickedY = 0;
UID pUID;
ICommandItem pCmdItem;
ICommandBars pCmdBars;
ICommand pCmd;
ITool pTool;
try
{
this.WindowState = FormWindowState.Minimized;
pCmdBars = m_pApp.Document.CommandBars;
pUID = new UIDClass();
pUID.Value = HVLR_Processing.ThisAddIn.IDs.clsMapPick;
pCmdItem = pCmdBars.Find(pUID);
if (pCmdItem != null)
{
m_pApp.CurrentTool = pCmdItem;
//pCmdItem.Execute();
dPickedX = m_pMxDoc.CurrentLocation.X;
dPickedY = m_pMxDoc.CurrentLocation.Y;
}
return sError;
}
This is working fine for me now, the Tool class is being called but the OnMouseDown event isn't being fired.
If you know why I'd appreciate it.

Related

Unity UI Button keeps double clicking, how can I work around this?

I have two buttons set up in an interactive fiction game, each press calls a new line of text. The problem is, every time I press on the button, I get two logged debug messages informing me of the click and my game moves two sections of text.
I've tried many different things to try to work around this including trying to alter the submit input in project settings and many different code forms. Any help would be greatly appreciated.
Here's my current code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AdventureGame : MonoBehaviour
{
[SerializeField]
private Text _textComponent;
[SerializeField]
private State _startingState;
private State state;
[SerializeField]
private Button _input0Button;
[SerializeField]
private Button _input1Button;
[SerializeField]
private Text _choices1;
[SerializeField]
private Text _choices2;
private bool _buttonOnePressed;
private bool _buttonTwoPressed;
void Start()
{
state = _startingState;
_textComponent.text = state.GetStateStory();
_input0Button.onClick.AddListener(Input0Button);
_input1Button.onClick.AddListener(Input1Button);
_buttonOnePressed = false;
_buttonTwoPressed = false;
}
void Update()
{
ManageState();
}
private void ManageState()
{
if (state._choice == true)
{
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
_input1Button.gameObject.SetActive(true);
if(_buttonOnePressed == true)
{
StartCoroutine(WaitForItOne());
}
else if(_buttonTwoPressed == true)
{
StartCoroutine(WaitForItTwo());
}
}
else if (state._choice == false)
{
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
_input1Button.gameObject.SetActive(false);
if(_buttonOnePressed == true)
{
StartCoroutine(WaitForItOne());
}
}
}
private void ManageChoiceOne()
{
_buttonOnePressed = false;
State[] _newState = state.GetNextStatesArray();
state = _newState[0];
}
private void ManageChoiceTwo()
{
_buttonTwoPressed = false;
State[] _newState = state.GetNextStatesArray();
state = _newState[1];
}
public void Input0Button()
{
Debug.Log("Input 0 pressed");
_buttonOnePressed = true;
}
public void Input1Button()
{
Debug.Log("Input 1 pressed");
_buttonTwoPressed = true;
}
IEnumerator WaitForItOne()
{
yield return new WaitForSeconds(3.0f);
ManageChoiceOne();
}
IEnumerator WaitForItTwo()
{
yield return new WaitForSeconds(3.0f);
ManageChoiceTwo();
}
}
First of all you keep starting new Coroutines each frame as long as e.g. _buttonOnePressed == true .. you wait 3 seconds before you finally unset this flag!
Then for the double call make sure the callbacks are not configured also in the Inspector! It seems like you have them once in the Inspector and additionally add them in your Start method so they are called twice!
Note that you won't see the callbacks added on runtime in the Inspector!
Why are you even using Update here at all? It is quite redundant to poll the state and the bool values and constantly check and handle their states in each frame. I would rather simply start the routine in the button method itself and make the whole code event driven instead!
(optionally) To give the user better feedback I would additionally in the meantime during the 3 seconds make the buttons non-interactable .. keep them active but not clickable:
// Remove state;
// Remove _buttonOnePressed
// Remove _buttonTwoPressed
private void Start()
{
// Either remove this two lines or the callbacks set in the Inspector
_input0Button.onClick.AddListener(Input0Button);
_input1Button.onClick.AddListener(Input1Button);
ManageState(_startingState);
}
// Remove Update
// This will be called only when actually needed
// since the state is passed in as parameter you don't need the private field
private void ManageState(State state)
{
// These happen in both cases anyway
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
// Here directly use the state flag
// since the button is disabled when needed
// there is no need for having different "states"
// since anyway only the according button(s) is(are) available
_input1Button.gameObject.SetActive(state._choice);
}
// (optional) Flag for avoiding concurrent routines
// Should be impossible since buttons get disabled but you never know
private bool alreadyHandlingButton;
private IEnumerator ManageChoice(bool isButtonOne)
{
// (optional) Skip if another routine running
if(alreadyHandlingButton) yield break;
// (optional) Block other routines just in cade
alreadyHandlingButton = true;
// Disable interactions
_input0Button.interactable = false;
_input1Button.interactable = false;
// This is the same for both buttons
yield return new WaitForSeconds(3f);
State[] _newState = state.GetNextStatesArray();
var state = _newState[isButtonOne ? 0 : 1];
// Only call ManageState when the state is actually changing
ManageState(state);
// (optional) Allow a new routine
alreadyHandlingButton = false;
// Enable interactions
_input0Button.interactable = true;
_input1Button.interactable = true;
}
public void Input0Button()
{
// (optional) Ignore if other button is already handled
if(alreadyHandlingButton) return;
Debug.Log("Input 0 pressed");
StartCoroutine(ManageChoice(true));
}
public void Input1Button()
{
// (optional) Ignore if other button is already handled
if(alreadyHandlingButton) return;
Debug.Log("Input 1 pressed");
StartCoroutine(ManageChoice(false));
}

How do I update a gtk listbox from an async method?

So when writing UI in GTK it's generally preferrable to handle reading of files, etc. in an Async Method. things such as listboxes, are generally bound to a ListModel, the items in the ListBox updated in accordance with the items_changed signal.
So if I have some class, that implements ListModel, and has an add function, and some FileReader that holds a reference to said ListModel, and call add from an async function, how do i make that in essence triggering the items_changed and having GTK update accordingly?
I've tried list.items_changed.connect(message("Items changed!")); but it never triggers.
I saw this: How can one update GTK+ UI in Vala from a long operation without blocking the UI
but in this example, it's just the button label that is changed, no signal is actually triggered.
EDIT: (Codesample added at the request of #Michael Gratton
//Disclaimer: everything here is still very much a work in progress, and will, as soon as I'm confident that what I have is not total crap, be released under some GPL or other open license.
//Note: for the sake of readability, I adopted the C# naming convention for interfaces, namely, putting a capital 'I' in front of them, a decision i do not feel quite as confident in as I did earlier.
//Note: the calls to message(..) was put in here to help debugging
public class AsyncFileContext : Object{
private int64 offset;
private bool start_read;
private bool read_to_end;
private Factories.IVCardFactory factory;
private File file;
private FileMonitor monitor;
private Gee.Set<IVCard> vcard_buffer;
private IObservableSet<IVCard> _vCards;
public IObservableSet<IVCard> vCards {
owned get{
return this._vCards;
}
}
construct{
//We want to start fileops at the beginning of the file
this.offset = (int64)0;
this.start_read = true;
this.read_to_end = false;
this.vcard_buffer = new Gee.HashSet<IVCard>();
this.factory = new Factories.GenericVCardFactory();
}
public void add_vcard(IVCard card){
//TODO: implement
}
public AsyncFileContext(IObservableSet<IVCard> vcards, string path){
this._vCards = vcards;
this._vCards = IObservableSet.wrap_set<IVCard>(new Gee.HashSet<IVCard>());
this.file = File.new_for_path(path);
this.monitor = file.monitor_file(FileMonitorFlags.NONE, null);
message("1");
//TODO: add connect
this.monitor.changed.connect((file, otherfile, event) => {
if(event != FileMonitorEvent.DELETED){
bool changes_done = event == FileMonitorEvent.CHANGES_DONE_HINT;
Idle.add(() => {
read_file_async.begin(changes_done);
return false;
});
}
});
message("2");
//We don't know that changes are done yet
//TODO: Consider carefully how you want this to work when it is NOT called from an event
Idle.add(() => {
read_file_async.begin(false);
return false;
});
}
//Changes done should only be true if the FileMonitorEvent that triggers the call was CHANGES_DONE_HINT
private async void read_file_async(bool changes_done) throws IOError{
if(!this.start_read){
return;
}
this.start_read = false;
var dis = new DataInputStream(yield file.read_async());
message("3");
//If we've been reading this file, and there's then a change, we assume we need to continue where we let off
//TODO: assert that the offset isn't at the very end of the file, if so reset to 0 so we can reread the file
if(offset > 0){
dis.seek(offset, SeekType.SET);
}
string line;
int vcards_added = 0;
while((line = yield dis.read_line_async()) != null){
message("position: %s".printf(dis.tell().to_string()));
this.offset = dis.tell();
message("4");
message(line);
//if the line is empty, we want to jump to next line, and ignore the input here entirely
if(line.chomp().chug() == ""){
continue;
}
this.factory.add_line(line);
if(factory.vcard_ready){
message("creating...");
this.vcard_buffer.add(factory.create());
vcards_added++;
//If we've read-in and created an entire vcard, it's time to yield
message("Yielding...");
Idle.add(() => {
_vCards.add_all(vcard_buffer);
vcard_buffer.remove_all(_vCards);
return false;
});
Idle.add(read_file_async.callback);
yield;
message("Resuming");
}
}
//IF we expect there will be no more writing, or if we expect that we read ALL the vcards, and did not add any, it's time to go back and read through the whole thing again.
if(changes_done){ //|| vcards_added == 0){
this.offset = 0;
}
this.start_read = true;
}
}
//The main idea in this class is to just bind the IObservableCollection's item_added, item_removed and cleared signals to the items_changed of the ListModel. IObservableCollection is a class I have implemented that merely wraps Gee.Collection, it is unittested, and works as intended
public class VCardListModel : ListModel, Object{
private Gee.List<IVCard> vcard_list;
private IObservableCollection<IVCard> vcard_collection;
public VCardListModel(IObservableCollection<IVCard> vcard_collection){
this.vcard_collection = vcard_collection;
this.vcard_list = new Gee.ArrayList<IVCard>.wrap(vcard_collection.to_array());
this.vcard_collection.item_added.connect((vcard) => {
vcard_list.add(vcard);
int pos = vcard_list.index_of(vcard);
items_changed(pos, 0, 1);
});
this.vcard_collection.item_removed.connect((vcard) => {
int pos = vcard_list.index_of(vcard);
vcard_list.remove(vcard);
items_changed(pos, 1, 0);
});
this.vcard_collection.cleared.connect(() => {
items_changed(0, vcard_list.size, 0);
vcard_list.clear();
});
}
public Object? get_item(uint position){
if((vcard_list.size - 1) < position){
return null;
}
return this.vcard_list.get((int)position);
}
public Type get_item_type(){
return Type.from_name("VikingvCardIVCard");
}
public uint get_n_items(){
return (uint)this.vcard_list.size;
}
public Object? get_object(uint position){
return this.get_item((int)position);
}
}
//The IObservableCollection parsed to this classes constructor, is the one from the AsyncFileContext
public class ContactList : Gtk.ListBox{
private ListModel list_model;
public ContactList(IObservableCollection<IVCard> ivcards){
this.list_model = new VCardListModel(ivcards);
bind_model(this.list_model, create_row_func);
list_model.items_changed.connect(() => {
message("Items Changed!");
base.show_all();
});
}
private Gtk.Widget create_row_func(Object item){
return new ContactRow((IVCard)item);
}
}
Heres the way i 'solved' it.
I'm not particularly proud of this solution, but there are a couple of awful things about the Gtk ListBox, one of them being (and this might really be more of a ListModel issue) if the ListBox is bound to a ListModel, the ListBox will NOT be sortable by using the sort method, and to me at least, that is a dealbreaker. I've solved it by making a class which is basically a List wrapper, which has an 'added' signal and a 'remove' signal. Upon adding an element to the list, the added signal is then wired, so it will create a new Row object and add it to the list box. That way, data is control in a manner Similar to ListModel binding. I can not make it work without calling the ShowAll method though.
private IObservableCollection<IVCard> _ivcards;
public IObservableCollection<IVCard> ivcards {
get{
return _ivcards;
}
set{
this._ivcards = value;
foreach(var card in this._ivcards){
base.prepend(new ContactRow(card));
}
this._ivcards.item_added.connect((item) => {
base.add(new ContactRow(item));
base.show_all();
});
base.show_all();
}
}
Even though this is by no means the best code I've come up with, it works very well.

How to hook up Microsoft Forms 2.0 event handlers using JScript

I'm trying to add a customized UI page to Sparx EA. It provides adding ActiveX controls via scripting. Using JScript, I've done this, but since ActiveX has to be registered on each client, I'd rather use Microsoft Forms, already installed on all clients.
I've successfully built the UI, appearance wise, by adding a "Forms.Form.1" ActiveX object, and adding text boxes, labels & buttons to the controls property of the created form.
These objects support events, but I can't figure out how to assign an event handler.
Here is the JScript code I used to get the screen layout:
function _addControl(parentControl, controlProgId, controlName, left, top, width, height){
var newControl = parentControl.controls.add(controlProgId, controlName,1);
newControl.Name=controlName;
newControl._SetLeft(left);
newControl._SetTop(top);
newControl._SetWidth(width);
newControl._SetHeight(height);
return newControl;
}
function main(){
//Create main form
var form = Repository.AddTab("ScriptedForm", "Forms.Form.1");
if (null != form){
//Add control
var textBox1 = _addControl(form, "Forms.TextBox.1","TextBox1", 18,21,94,93);
var textBox2 = _addControl(form, "Forms.TextBox.1","TextBox2", 120, 21, 91, 93);
var btnTest = _addControl(form, "Forms.CommandButton.1", "btnTest", 60, 140, 90, 30);
btnTest.Caption = "Test";
//Here's where I assign the click event, but it's unhappy.
btnTest.add_Click(this.TextBox1_Click);
}
}
function TextBox1_Click(Object){
Session.Prompt("Click", promptOK);
}
The add_Click event expects a parameter of type CommandButtonEvents_ClickEventHandler.
There's nothing I can create that could be submitted as the parameter. I tried creating a JScript class duplicating the interface, but no joy.
I think you are running into several problems here at once.
(1) Process Lifetime
As far as I understand your question and its context, you are somehow manually executing a JScript script. Doing this EA will internally start SScripter.exe. You see this in the Debug window:
The process is effectively terminated after the script finishes (and thus also terminating any event handlers you might have registered in your UserControl or Form object).
(2) Passing a JScript object instance as a .NET delegate
If you could somehow extend the lifetime of the script environment, and if you could pass something to your event you will realise that any object in your JScript code will be passed as a System.__ComObject to the .NET runtime inside EA. Therefore you cannot just register an event handler.
However when you evaluate the object from .NET you will find out it is not an IDispatch interface:
MemberNames:
ToString,
GetLifetimeService,
InitializeLifetimeService,
CreateObjRef,
Equals,
GetHashCode,
GetType
TargetInvocationException#mscorlib: 'COM target does not implement IDispatch.'
I did a small test with the code below:
function MyClass(name)
{
this.name = name;
}
MyClass.prototype.Invoke = function(value)
{
Session.Output("name " + value);
return true;
}
function main()
{
var myClass = new MyClass("Hotzenplotz");
myClass.Invoke("some Value");
var ctrl = new ActiveXObject("IMASE.TestUserControl2");
ctrl.Repository = Repository;
ctrl.JavaScriptObject = myClass;
}
[ProgId(Global.ADDIN_NAME + Global.DOT + "TestUserControl2")]
[Guid("87156dd9-e947-44bf-92a9-e9554a5b1844")]
[ComVisible(true)]
public partial class TestUserControl2 : ActivexControl
{
public static string TabName { get; } = Global.ADDIN_NAME;
private static readonly Lazy<string> _controlId = new Lazy<string>(() =>
{
var attribute = typeof(TestUserControl).GetCustomAttribute<ProgIdAttribute>();
return attribute.Value;
});
private Timer timer;
public static string ControlId = _controlId.Value;
public Repository Repository { get; set; }
public object JavaScriptObject { get; set; }
public TestUserControl2()
{
timer = new Timer();
timer.Elapsed += TimerEvent;
timer.Interval = 5000;
timer.Enabled = true;
timer.Start();
}
~TestUserControl2()
{
Logger.Default.TraceInformation("I'm gonna die ... " + this.GetHashCode());
}
private void OnDispose(object sender, EventArgs e)
{
timer.Dispose();
}
private void TimerEvent(object source, ElapsedEventArgs e)
{
Logger.Default.TraceInformation("I'm still alive ... " + this.GetHashCode());
if(null == JavaScriptObject) return;
try
{
var memberNames = JavaScriptObject.GetType().GetMembers(BindingFlags.Instance|BindingFlags.FlattenHierarchy|BindingFlags.Public).Select(p => p.Name);
Logger.Default.TraceInformation("memberNames: " + string.Join(", ", memberNames));
var result = JavaScriptObject.GetType().InvokeMember("Invoke", BindingFlags.InvokeMethod, null, JavaScriptObject, new object[] {"arbitraryString"});
Logger.Default.TraceInformation("result: " + result);
}
catch (Exception ex)
{
Logger.Default.TraceException(ex);
}
}
}
(3) Another approach
Create a UserControl inside your addin (using WinForm or Forms) and use ClearScript as a ScriptEngine.
Pass Session and Repository from a EA script to your control (or do it otherwise such as a menu to wprk around the lifetime problem) and have either your forms code load a script from the repository (or any other source). Then react on your event handlers to execute your JScript code as needed. I create a simple example that shows how to call a control from an EA JScript and call another JScript from inside your forms code that in turn will log to the Debug session or regular scripting output window:
function main()
{
var ctrl = new ActiveXObject("IMASE.TestUserControl2");
ctrl.Repository = Repository;
ctrl.Session = Session;
Session.Prompt("wait", promptOK);
}
main();
Inside your form code you invoke your JScript with Repository and other objects like this:
public Repository Repository { get; set; }
public object Session { get; set; }
using (var engine = new JScriptEngine())
{
engine.AddHostObject("Repository", this.Repository);
engine.AddHostObject("Session", this.Session);
engine.Execute("Session.Output('Repository.ConnectionString: ' + Repository.ConnectionString);");
}
Here is an output of the above scripting interactions:
Side note: I personally do not see the need for using forms as we can dynamically register ActiveX controls at AddIn startup. For code on doing this you can have a look at the following gist:
https://gist.github.com/dfch/6a27bb1b9320c93456cee6d5b2b9d551
In addition, if you are using ClearScript as the script host, you can directly connect to your (UI) events from your script code as described in question #16 of the ClearScript FAQtorial.

How can I create an object at the mouse point in scene view by clicking not dragging the object?

Generally, most objects are placed in the scene view by dragging or something. I want to right click the mouse (without dragging an object) to create an object in the scene view. I know this would require some editor coding but I’m not sure how to go about it.
UPDATE
After giving it some thought I realised that using a MenuItem would be quite appropriate for me. Here is my code below:
SLMenuItems:
public class SLMenuItems : MonoBehaviour {
public bool canClickSceneViewToCreatePath = false;
void Start()
{
}
[MenuItem("Component/Create Custom Object")]
static void CreateObject() {
Debug.Log("menu item selected");
canClickSceneViewToCreatePath = true;
}
}
SLMenuItemsEditor:
[CustomEditor(typeof(SLMenuItems))]
public class SLMenuItemsEditor : Editor {
SLMenuItems slMenuItems;
void OnEnable()
{
slMenuItems = (SLMenuItems)target;
}
void OnSceneGUI()
{
if (slMenuItems.canClickSceneViewToCreatePath) {
Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
// create object here at pointsPos
slMenuItems.canClickSceneViewToCreatePath = false;
}
}
}
}
I keep getting the following error:
Assets/SLMenuItems.cs(23,9): error CS0120: An object reference is required to access non-static member `SLMenuItems.canClickSceneViewToCreatePath'
pointing to the line:
canClickSceneViewToCreatePath = true;
in SLMenuItems.
Your CreateObject method is static but your canClickSceneViewToCreatePath value is not.
It has nothing to do with the editor script but with your class SlMenuItems itself.
A static method is not instanced or with other words it is kind of shared between all instances of that component type while the non-static value might be different for each component.
So how should a static method - which is the same for all instances - "know", which of the instances values it should access?
So either make the method non-static or the variable static. Depending on what your further need is.
Since the MenuItem needs a static method make the variable static as well.
I would suggest you make that class not inherit from MonoBehaviour at all since it doesn't have any behaviour for a GameObject. It only brings some editor features so rather make it a static class that can "live" in the Assets without needing to be instanced.
Than you can use SceneView.onSceneGUIDelegate to register a callback for OnSceneGUI without implementing an editor script for that:
private static GameObject lastCreated;
private static bool isCreating;
public static class SLMenuItems
{
[MenuItem("Component/Create Custom Object")]
private static void CreateObject()
{
Debug.Log("menu item selected");
isCreating = true;
lastCreated = null;
// Add a callback for SceneView update
SceneView.onSceneGUIDelegate -= UpdateSceneView;
SceneView.onSceneGUIDelegate += UpdateSceneView;
}
private static void UpdateSceneView(SceneView sceneView)
{
if(lastCreated)
{
// Keep lastCreated focused
Selection.activeGameObject = lastCreated;
}
if(isCreating)
{
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
//Todo create object here at pointsPos
lastCreated = Instantiate(somePrefab);
// Avoid the current event being propagated
// I'm not sure which of both works better here
Event.current.Use();
Event.current = null;
// Keep the created object in focus
Selection.activeGameObject = lastCreated;
// exit creation mode
isCreating = false;
}
}
else
{
// Skip if event is Layout or Repaint
if(e.type == EventType.Layout || e.type == EventType.Repaint)
{
Selection.activeGameObject = lastCreated;
return;
}
// Prevent Propagation
Event.current.Use();
Event.current = null;
Selection.activeGameObject = lastCreated;
lastCreated = null;
// Remove the callback
SceneView.onSceneGUIDelegate -= UpdateSceneView;
}
}
}
But I suggest you change your questions title since this is actually not the solution to the "task" you describe before.

Powershell monitoring for file system changes [duplicate]

I have an application where I am looking for a text file and if there are any changes made to the file I am using the OnChanged eventhandler to handle the event. I am using the NotifyFilters.LastWriteTime but still the event is getting fired twice. Here is the code.
public void Initialize()
{
FileSystemWatcher _fileWatcher = new FileSystemWatcher();
_fileWatcher.Path = "C:\\Folder";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Filter = "Version.txt";
_fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
_fileWatcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
.......
}
In my case the OnChanged is called twice, when I change the text file version.txt and save it.
I am afraid that this is a well-known bug/feature of the FileSystemWatcher class. This is from the documentation of the class:
You may notice in certain situations that a single creation event generates multiple Created events that are handled by your component. For example, if you use a FileSystemWatcher component to monitor the creation of new files in a directory, and then test it by using Notepad to create a file, you may see two Created events generated even though only a single file was created. This is because Notepad performs multiple file system actions during the writing process. Notepad writes to the disk in batches that create the content of the file and then the file attributes. Other applications may perform in the same manner. Because FileSystemWatcher monitors the operating system activities, all events that these applications fire will be picked up.
Now this bit of text is about the Created event, but the same thing applies to other file events as well. In some applications you might be able to get around this by using the NotifyFilter property, but my experience is says that sometimes you have to do some manual duplicate filtering (hacks) as well.
A while ago I bookedmarked a page with a few FileSystemWatcher tips. You might want to check it out.
I've "fixed" that problem using the following strategy in my delegate:
// fsw_ is the FileSystemWatcher instance used by my application.
private void OnDirectoryChanged(...)
{
try
{
fsw_.EnableRaisingEvents = false;
/* do my stuff once asynchronously */
}
finally
{
fsw_.EnableRaisingEvents = true;
}
}
Any duplicated OnChanged events from the FileSystemWatcher can be detected and discarded by checking the File.GetLastWriteTime timestamp on the file in question. Like so:
DateTime lastRead = DateTime.MinValue;
void OnChanged(object source, FileSystemEventArgs a)
{
DateTime lastWriteTime = File.GetLastWriteTime(uri);
if (lastWriteTime != lastRead)
{
doStuff();
lastRead = lastWriteTime;
}
// else discard the (duplicated) OnChanged event
}
Here is my solution which helped me to stop the event being raised twice:
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
Here I have set the NotifyFilter property with only Filename and size.
watcher is my object of FileSystemWatcher. Hope this will help.
Here's my approach :
// Consider having a List<String> named _changedFiles
private void OnChanged(object source, FileSystemEventArgs e)
{
lock (_changedFiles)
{
if (_changedFiles.Contains(e.FullPath))
{
return;
}
_changedFiles.Add(e.FullPath);
}
// do your stuff
System.Timers.Timer timer = new Timer(1000) { AutoReset = false };
timer.Elapsed += (timerElapsedSender, timerElapsedArgs) =>
{
lock (_changedFiles)
{
_changedFiles.Remove(e.FullPath);
}
};
timer.Start();
}
This is the solution I used to solve this issue on a project where I was sending the file as attachment in a mail.
It will easily avoid the twice fired event even with a smaller timer interval but in my case 1000 was alright since I was happier with missing few changes than with flooding the mailbox with > 1 message per second.
At least it works just fine in case several files are changed at the exact same time.
Another solution I've thought of would be to replace the list with a dictionary mapping files to their respective MD5, so you wouldn't have to choose an arbitrary interval since you wouldn't have to delete the entry but update its value, and cancel your stuff if it hasn't changed.
It has the downside of having a Dictionary growing in memory as files are monitored and eating more and more memory, but I've read somewhere that the amount of files monitored depends on the FSW's internal buffer, so maybe not that critical.
Dunno how MD5 computing time would affect your code's performances either, careful =\
My scenario is that I have a virtual machine with a Linux server in it. I am developing files on the Windows host. When I change something in a folder on the host I want all the changes to be uploaded, synced onto the virtual server via Ftp. This is how I do eliminate the duplicate change event when I write to a file ( which flags the folder containing the file to be modified as well ) :
private Hashtable fileWriteTime = new Hashtable();
private void fsw_sync_Changed(object source, FileSystemEventArgs e)
{
string path = e.FullPath.ToString();
string currentLastWriteTime = File.GetLastWriteTime( e.FullPath ).ToString();
// if there is no path info stored yet
// or stored path has different time of write then the one now is inspected
if ( !fileWriteTime.ContainsKey(path) ||
fileWriteTime[path].ToString() != currentLastWriteTime
)
{
//then we do the main thing
log( "A CHANGE has occured with " + path );
//lastly we update the last write time in the hashtable
fileWriteTime[path] = currentLastWriteTime;
}
}
Mainly I create a hashtable to store file write time information. Then if the hashtable has the filepath that is modified and it's time value is the same as the currently notified file's change then I know it is the duplicate of the event and ignore it.
I have created a Git repo with a class that extends FileSystemWatcher to trigger the events only when copy is done. It discards all the changed events exept the last and it raise it only when the file become available for read.
Download FileSystemSafeWatcher and add it to your project.
Then use it as a normal FileSystemWatcher and monitor when the events are triggered.
var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
Try with this code:
class WatchPlotDirectory
{
bool let = false;
FileSystemWatcher watcher;
string path = "C:/Users/jamie/OneDrive/Pictures/Screenshots";
public WatchPlotDirectory()
{
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = "*.*";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
watcher.EnableRaisingEvents = true;
}
void OnChanged(object sender, FileSystemEventArgs e)
{
if (let==false) {
string mgs = string.Format("File {0} | {1}",
e.FullPath, e.ChangeType);
Console.WriteLine("onchange: " + mgs);
let = true;
}
else
{
let = false;
}
}
void OnRenamed(object sender, RenamedEventArgs e)
{
string log = string.Format("{0} | Renamed from {1}",
e.FullPath, e.OldName);
Console.WriteLine("onrenamed: " + log);
}
public void setPath(string path)
{
this.path = path;
}
}
I know this is an old issue, but had the same problem and none of the above solution really did the trick for the problem I was facing. I have created a dictionary which maps the file name with the LastWriteTime. So if the file is not in the dictionary will go ahead with the process other wise check to see when was the last modified time and if is different from what it is in the dictionary run the code.
Dictionary<string, DateTime> dateTimeDictionary = new Dictionary<string, DateTime>();
private void OnChanged(object source, FileSystemEventArgs e)
{
if (!dateTimeDictionary.ContainsKey(e.FullPath) || (dateTimeDictionary.ContainsKey(e.FullPath) && System.IO.File.GetLastWriteTime(e.FullPath) != dateTimeDictionary[e.FullPath]))
{
dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
//your code here
}
}
One possible 'hack' would be to throttle the events using Reactive Extensions for example:
var watcher = new FileSystemWatcher("./");
Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
.Throttle(new TimeSpan(500000))
.Subscribe(HandleChangeEvent);
watcher.EnableRaisingEvents = true;
In this case I'm throttling to 50ms, on my system that was enough, but higher values should be safer. (And like I said, it's still a 'hack').
I spent some significant amount of time using the FileSystemWatcher, and some of the approaches here will not work. I really liked the disabling events approach, but unfortunately, it doesn't work if there is >1 file being dropped, second file will be missed most if not all times.
So I use the following approach:
private void EventCallback(object sender, FileSystemEventArgs e)
{
var fileName = e.FullPath;
if (!File.Exists(fileName))
{
// We've dealt with the file, this is just supressing further events.
return;
}
// File exists, so move it to a working directory.
File.Move(fileName, [working directory]);
// Kick-off whatever processing is required.
}
I have a very quick and simple workaround here, it does work for me, and no matter the event would be triggered once or twice or more times occasionally, check it out:
private int fireCount = 0;
private void inputFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
fireCount++;
if (fireCount == 1)
{
MessageBox.Show("Fired only once!!");
dowork();
}
else
{
fireCount = 0;
}
}
}
Here is a new solution you can try. Works well for me. In the event handler for the changed event programmatically remove the handler from the designer output a message if desired then programmatically add the handler back. example:
public void fileSystemWatcher1_Changed( object sender, System.IO.FileSystemEventArgs e )
{
fileSystemWatcher1.Changed -= new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
MessageBox.Show( "File has been uploaded to destination", "Success!" );
fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
}
The main reason was
first event's last access time was current time(file write or changed time).
then second event was file's original last access time.
I solve under code.
var lastRead = DateTime.MinValue;
Watcher = new FileSystemWatcher(...)
{
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
Filter = "*.dll",
IncludeSubdirectories = false,
};
Watcher.Changed += (senderObject, ea) =>
{
var now = DateTime.Now;
var lastWriteTime = File.GetLastWriteTime(ea.FullPath);
if (now == lastWriteTime)
{
return;
}
if (lastWriteTime != lastRead)
{
// do something...
lastRead = lastWriteTime;
}
};
Watcher.EnableRaisingEvents = true;
This code worked for me.
private void OnChanged(object source, FileSystemEventArgs e)
{
string fullFilePath = e.FullPath.ToString();
string fullURL = buildTheUrlFromStudyXML(fullFilePath);
System.Diagnostics.Process.Start("iexplore", fullURL);
Timer timer = new Timer();
((FileSystemWatcher)source).Changed -= new FileSystemEventHandler(OnChanged);
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(t_Elapsed);
timer.Start();
}
private void t_Elapsed(object sender, ElapsedEventArgs e)
{
((Timer)sender).Stop();
theWatcher.Changed += new FileSystemEventHandler(OnChanged);
}
mostly for future me :)
I wrote a wrapper using Rx:
public class WatcherWrapper : IDisposable
{
private readonly FileSystemWatcher _fileWatcher;
private readonly Subject<FileSystemEventArgs> _infoSubject;
private Subject<FileSystemEventArgs> _eventSubject;
public WatcherWrapper(string path, string nameFilter = "*.*", NotifyFilters? notifyFilters = null)
{
_fileWatcher = new FileSystemWatcher(path, nameFilter);
if (notifyFilters != null)
{
_fileWatcher.NotifyFilter = notifyFilters.Value;
}
_infoSubject = new Subject<FileSystemEventArgs>();
_eventSubject = new Subject<FileSystemEventArgs>();
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Changed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Created").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Deleted").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Renamed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
// this takes care of double events and still works with changing the name of the same file after a while
_infoSubject.Buffer(TimeSpan.FromMilliseconds(20))
.Select(x => x.GroupBy(z => z.FullPath).Select(z => z.LastOrDefault()).Subscribe(
infos =>
{
if (infos != null)
foreach (var info in infos)
{
{
_eventSubject.OnNext(info);
}
}
});
_fileWatcher.EnableRaisingEvents = true;
}
public IObservable<FileSystemEventArgs> FileEvents => _eventSubject;
public void Dispose()
{
_fileWatcher?.Dispose();
_eventSubject.Dispose();
_infoSubject.Dispose();
}
}
Usage:
var watcher = new WatcherWrapper(_path, "*.info");
// all more complicated and scenario specific filtering of events can be done here
watcher.FileEvents.Where(x => x.ChangeType != WatcherChangeTypes.Deleted).Subscribe(x => //do stuff)
Try this, It's working fine
private static readonly FileSystemWatcher Watcher = new FileSystemWatcher();
static void Main(string[] args)
{
Console.WriteLine("Watching....");
Watcher.Path = #"D:\Temp\Watcher";
Watcher.Changed += OnChanged;
Watcher.EnableRaisingEvents = true;
Console.ReadKey();
}
static void OnChanged(object sender, FileSystemEventArgs e)
{
try
{
Watcher.Changed -= OnChanged;
Watcher.EnableRaisingEvents = false;
Console.WriteLine($"File Changed. Name: {e.Name}");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
finally
{
Watcher.Changed += OnChanged;
Watcher.EnableRaisingEvents = true;
}
}
I wanted to react only on the last event, just in case, also on a linux file change it seemed that the file was empty on the first call and then filled again on the next and did not mind loosing some time just in case the OS decided to do some file/attribute change.
I am using .NET async here to help me do the threading.
private static int _fileSystemWatcherCounts;
private async void OnChanged(object sender, FileSystemEventArgs e)
{
// Filter several calls in short period of time
Interlocked.Increment(ref _fileSystemWatcherCounts);
await Task.Delay(100);
if (Interlocked.Decrement(ref _fileSystemWatcherCounts) == 0)
DoYourWork();
}
I think the best solution to solve the issue is to use reactive extensions
When you transform event into observable, then you can just add Throttling(..) (originally called Debounce(..))
Sample code here
var templatesWatcher = new FileSystemWatcher(settingsSnapshot.Value.TemplatesDirectory)
{
NotifyFilter = NotifyFilters.LastWrite,
IncludeSubdirectories = true
};
templatesWatcher.EnableRaisingEvents = true;
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
addHandler => templatesWatcher.Changed += addHandler,
removeHandler => templatesWatcher.Changed -= removeHandler)
.Throttle(TimeSpan.FromSeconds(5))
.Subscribe(args =>
{
_logger.LogInformation($"Template file {args.EventArgs.Name} has changed");
//TODO do something
});
You could try to open it for write, and if successful then you could assume the other application is done with the file.
private void OnChanged(object source, FileSystemEventArgs e)
{
try
{
using (var fs = File.OpenWrite(e.FullPath))
{
}
//do your stuff
}
catch (Exception)
{
//no write access, other app not done
}
}
Just opening it for write appears not to raise the changed event. So it should be safe.
FileReadTime = DateTime.Now;
private void File_Changed(object sender, FileSystemEventArgs e)
{
var lastWriteTime = File.GetLastWriteTime(e.FullPath);
if (lastWriteTime.Subtract(FileReadTime).Ticks > 0)
{
// code
FileReadTime = DateTime.Now;
}
}
Sorry for the grave dig, but I've been battling this issue for a while now and finally came up with a way to handle these multiple fired events. I would like to thank everyone in this thread as I have used it in many references when battling this issue.
Here is my complete code. It uses a dictionary to track the date and time of the last write of the file. It compares that value, and if it is the same, it suppresses the events. It then sets the value after starting the new thread.
using System.Threading; // used for backgroundworker
using System.Diagnostics; // used for file information
private static IDictionary<string, string> fileModifiedTable = new Dictionary<string, string>(); // used to keep track of our changed events
private void fswFileWatch_Changed( object sender, FileSystemEventArgs e )
{
try
{
//check if we already have this value in our dictionary.
if ( fileModifiedTable.TryGetValue( e.FullPath, out sEmpty ) )
{
//compare timestamps
if ( fileModifiedTable[ e.FullPath ] != File.GetLastWriteTime( e.FullPath ).ToString() )
{
//lock the table
lock ( fileModifiedTable )
{
//make sure our file is still valid
if ( File.Exists( e.FullPath ) )
{
// create a new background worker to do our task while the main thread stays awake. Also give it do work and work completed handlers
BackgroundWorker newThreadWork = new BackgroundWorker();
newThreadWork.DoWork += new DoWorkEventHandler( bgwNewThread_DoWork );
newThreadWork.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bgwNewThread_RunWorkerCompleted );
// capture the path
string eventFilePath = e.FullPath;
List<object> arguments = new List<object>();
// add arguments to pass to the background worker
arguments.Add( eventFilePath );
arguments.Add( newEvent.File_Modified );
// start the new thread with the arguments
newThreadWork.RunWorkerAsync( arguments );
fileModifiedTable[ e.FullPath ] = File.GetLastWriteTime( e.FullPath ).ToString(); //update the modified table with the new timestamp of the file.
FILE_MODIFIED_FLAG.WaitOne(); // wait for the modified thread to complete before firing the next thread in the event multiple threads are being worked on.
}
}
}
}
}
catch ( IOException IOExcept )
{
//catch any errors
postError( IOExcept, "fswFileWatch_Changed" );
}
}
Event if not asked, it is a shame there are no ready solution samples for F#.
To fix this here is my recipe, just because I can and F# is a wonderful .NET language.
Duplicated events are filtered out using FSharp.Control.Reactive package, which is just a F# wrapper for reactive extensions. All that can be targeted to full framework or netstandard2.0:
let createWatcher path filter () =
new FileSystemWatcher(
Path = path,
Filter = filter,
EnableRaisingEvents = true,
SynchronizingObject = null // not needed for console applications
)
let createSources (fsWatcher: FileSystemWatcher) =
// use here needed events only.
// convert `Error` and `Renamed` events to be merded
[| fsWatcher.Changed :> IObservable<_>
fsWatcher.Deleted :> IObservable<_>
fsWatcher.Created :> IObservable<_>
//fsWatcher.Renamed |> Observable.map renamedToNeeded
//fsWatcher.Error |> Observable.map errorToNeeded
|] |> Observable.mergeArray
let handle (e: FileSystemEventArgs) =
printfn "handle %A event '%s' '%s' " e.ChangeType e.Name e.FullPath
let watch path filter throttleTime =
// disposes watcher if observer subscription is disposed
Observable.using (createWatcher path filter) createSources
// filter out multiple equal events
|> Observable.distinctUntilChanged
// filter out multiple Changed
|> Observable.throttle throttleTime
|> Observable.subscribe handle
[<EntryPoint>]
let main _args =
let path = #"C:\Temp\WatchDir"
let filter = "*.zip"
let throttleTime = TimeSpan.FromSeconds 10.
use _subscription = watch path filter throttleTime
System.Console.ReadKey() |> ignore
0 // return an integer exit code
In my case need to get the last line of a text file that is inserted by other application, as soon as insertion is done. Here is my solution. When the first event is raised, i disable the watcher from raising others, then i call the timer TimeElapsedEvent because when my handle function OnChanged is called i need the size of the text file, but the size at that time is not the actual size, it is the size of the file imediatelly before the insertion. So i wait for a while to proceed with the right file size.
private FileSystemWatcher watcher = new FileSystemWatcher();
...
watcher.Path = "E:\\data";
watcher.NotifyFilter = NotifyFilters.LastWrite ;
watcher.Filter = "data.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
...
private void OnChanged(object source, FileSystemEventArgs e)
{
System.Timers.Timer t = new System.Timers.Timer();
try
{
watcher.Changed -= new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = false;
t.Interval = 500;
t.Elapsed += (sender, args) => t_Elapsed(sender, e);
t.Start();
}
catch(Exception ex) {
;
}
}
private void t_Elapsed(object sender, FileSystemEventArgs e)
{
((System.Timers.Timer)sender).Stop();
//.. Do you stuff HERE ..
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
Here is another approach. Instead of propagating the first event of a quick succession of events and suppressing all that follow, now all are suppressed except from the last one. I think that the scenarios that can benefit from this approach are more common.
To make this happen we must use a sliding delay. Every incoming event cancels the timer that would fire the previous event, and restarts the timer. This opens the possibility that a never-ending series of events will delay the propagation forever. To keep things simple, there is no provision for this abnormal case in the extension methods below.
public static class FileSystemWatcherExtensions
{
public static IDisposable OnAnyEvent(this FileSystemWatcher source,
WatcherChangeTypes changeTypes, FileSystemEventHandler handler, int delay)
{
var cancellations = new Dictionary<string, CancellationTokenSource>(
StringComparer.OrdinalIgnoreCase);
var locker = new object();
if (changeTypes.HasFlag(WatcherChangeTypes.Created))
source.Created += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Deleted))
source.Deleted += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Changed))
source.Changed += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Renamed))
source.Renamed += FileSystemWatcher_Event;
return new Disposable(() =>
{
source.Created -= FileSystemWatcher_Event;
source.Deleted -= FileSystemWatcher_Event;
source.Changed -= FileSystemWatcher_Event;
source.Renamed -= FileSystemWatcher_Event;
});
async void FileSystemWatcher_Event(object sender, FileSystemEventArgs e)
{
var key = e.FullPath;
var cts = new CancellationTokenSource();
lock (locker)
{
if (cancellations.TryGetValue(key, out var existing))
{
existing.Cancel();
}
cancellations[key] = cts;
}
try
{
await Task.Delay(delay, cts.Token);
// Omitting ConfigureAwait(false) is intentional here.
// Continuing in the captured context is desirable.
}
catch (TaskCanceledException)
{
return;
}
lock (locker)
{
if (cancellations.TryGetValue(key, out var existing)
&& existing == cts)
{
cancellations.Remove(key);
}
}
cts.Dispose();
handler(sender, e);
}
}
public static IDisposable OnAllEvents(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.All, handler, delay);
public static IDisposable OnCreated(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Created, handler, delay);
public static IDisposable OnDeleted(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Deleted, handler, delay);
public static IDisposable OnChanged(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Changed, handler, delay);
public static IDisposable OnRenamed(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Renamed, handler, delay);
private struct Disposable : IDisposable
{
private readonly Action _action;
internal Disposable(Action action) => _action = action;
public void Dispose() => _action?.Invoke();
}
}
Usage example:
myWatcher.OnAnyEvent(WatcherChangeTypes.Created | WatcherChangeTypes.Changed,
MyFileSystemWatcher_Event, 100);
This line combines the subscription to two events, the Created and the Changed. So it is roughly equivalent to these:
myWatcher.Created += MyFileSystemWatcher_Event;
myWatcher.Changed += MyFileSystemWatcher_Event;
The difference is that the two events are regarded as a single type of event, and in case of a quick succession of these events only the last one will be propagated. For example if a Created event is followed by two Changed events, and there is no time gap larger than 100 msec between these three events, only the second Changed event will be propagated by invoking the MyFileSystemWatcher_Event handler, and the previous ones will be discarded.
I have changed the way I monitor files in directories. Instead of using the FileSystemWatcher I poll locations on another thread and then look at the LastWriteTime of the file.
DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);
Using this information and keeping an index of a file path and it's latest write time I can determine files that have changed or that have been created in a particular location. This removes me from the oddities of the FileSystemWatcher. The main downside is that you need a data structure to store the LastWriteTime and the reference to the file, but it is reliable and easy to implement.
I was able to do this by added a function that checks for duplicates in an buffer array.
Then perform the action after the array has not been modified for X time using a timer:
- Reset timer every time something is written to the buffer
- Perform action on tick
This also catches another duplication type. If you modify a file inside a folder, the folder also throws a Change event.
Function is_duplicate(str1 As String) As Boolean
If lb_actions_list.Items.Count = 0 Then
Return False
Else
Dim compStr As String = lb_actions_list.Items(lb_actions_list.Items.Count - 1).ToString
compStr = compStr.Substring(compStr.IndexOf("-") + 1).Trim
If compStr <> str1 AndAlso compStr.parentDir <> str1 & "\" Then
Return False
Else
Return True
End If
End If
End Function
Public Module extentions
<Extension()>
Public Function parentDir(ByVal aString As String) As String
Return aString.Substring(0, CInt(InStrRev(aString, "\", aString.Length - 1)))
End Function
End Module
This solution worked for me on production application:
Environment:
VB.Net Framework 4.5.2
Set manually object properties: NotifyFilter = Size
Then use this code:
Public Class main
Dim CalledOnce = False
Private Sub FileSystemWatcher1_Changed(sender As Object, e As IO.FileSystemEventArgs) Handles FileSystemWatcher1.Changed
If (CalledOnce = False) Then
CalledOnce = True
If (e.ChangeType = 4) Then
' Do task...
CalledOnce = False
End If
End Sub
End Sub
Try this!
string temp="";
public void Initialize()
{
FileSystemWatcher _fileWatcher = new FileSystemWatcher();
_fileWatcher.Path = "C:\\Folder";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Filter = "Version.txt";
_fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
_fileWatcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
.......
if(temp=="")
{
//do thing you want.
temp = e.name //name of text file.
}else if(temp !="" && temp != e.name)
{
//do thing you want.
temp = e.name //name of text file.
}else
{
//second fire ignored.
}
}
I had to combine several ideas from the posts above and add file locking check to get it working for me:
FileSystemWatcher fileSystemWatcher;
private void DirectoryWatcher_Start()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher
{
Path = #"c:\mypath",
NotifyFilter = NotifyFilters.LastWrite,
Filter = "*.*",
EnableRaisingEvents = true
};
fileSystemWatcher.Changed += new FileSystemEventHandler(DirectoryWatcher_OnChanged);
}
private static void WaitUntilFileIsUnlocked(String fullPath, Action<String> callback, FileAccess fileAccess = FileAccess.Read, Int32 timeoutMS = 10000)
{
Int32 waitMS = 250;
Int32 currentMS = 0;
FileInfo file = new FileInfo(fullPath);
FileStream stream = null;
do
{
try
{
stream = file.Open(FileMode.Open, fileAccess, FileShare.None);
stream.Close();
callback(fullPath);
return;
}
catch (IOException)
{
}
finally
{
if (stream != null)
stream.Dispose();
}
Thread.Sleep(waitMS);
currentMS += waitMS;
} while (currentMS < timeoutMS);
}
private static Dictionary<String, DateTime> DirectoryWatcher_fileLastWriteTimeCache = new Dictionary<String, DateTime>();
private void DirectoryWatcher_OnChanged(Object source, FileSystemEventArgs ev)
{
try
{
lock (DirectoryWatcher_fileLastWriteTimeCache)
{
DateTime lastWriteTime = File.GetLastWriteTime(ev.FullPath);
if (DirectoryWatcher_fileLastWriteTimeCache.ContainsKey(ev.FullPath))
{
if (DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath].AddMilliseconds(500) >= lastWriteTime)
return; // file was already handled
}
DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath] = lastWriteTime;
}
Task.Run(() => WaitUntilFileIsUnlocked(ev.FullPath, fullPath =>
{
// do the job with fullPath...
}));
}
catch (Exception e)
{
// handle exception
}
}