MS Project - Disable resource - c#

I am creating a Microsoft Project add-in. Depending on the value of a resource's custom text column (let's say Text30), can I disable a resource from showing up when trying to assign it to a task?
So for example, if a row has a Text30 value of Inactive, it shouldn't be available to the Project file user to assign it to a task, but it should still be in the Resource Sheet. Below, row 1 would be available as a resource, and row 2 wouldn't show up when assigning resources to a task.

With the help of Rachel's linked question I managed to find a workaround. I added a Text column called Active Status (Text30 in my case). Then, using Application.ProjectBeforeTaskChange I checked if the new value is a resource that has the column Text30 of value Active. Here is the link to the MS Project event. Below is the code:
Imports Microsoft.Office.Interop.MSProject
Private Sub Application_ProjectBeforeTaskChange(tsk As Task, Field As PjField, NewVal As Object, ByRef Cancel As Boolean) Handles Application.ProjectBeforeTaskChange
CheckResourceValidity(NewVal, Cancel)
End Sub
Private Sub CheckResourceValidity(NewVal As Object, ByRef Cancel As Boolean)
Dim res As Resource
Dim newValList = NewVal.Split(",")
If Not IsStartup(newValList) Then
For Each splitNewVal In newValList
' currentProject is the currently active Project
res = currentProject.Resources.Item(splitNewVal)
If splitNewVal = res.Name Then
If res.Text30 <> "Active" Then
MessageBox.Show("You are trying to assign an inactive resource to a task. Please choose an active resource.",
"Assigning inactive resource", MessageBoxButtons.OK, MessageBoxIcon.Error)
Cancel = True
End If
End If
Next
End If
End Sub
' The event ProjectBeforeTaskChange is called when a .mpp file with data inside is opened
Private Function IsStartup(newValList As Object) As Boolean
Dim res As Resource
For Each x In newValList
Try
res = GlobalVariables.currentProject.ProjResources.Item(x)
Catch ex As System.Runtime.InteropServices.COMException
Return True
End Try
Next
Return False
End Function
The program will not allow the user to add a resource to a task if the resource doesn't have a Text30 value of 'Active'.
ATTENTION:
This event will trigger only if you assign the resource through the Gantt Chart. If you assign through the Resource tab -> Assign Resource, the event won't be triggered. The event ProjectBeforeAssignmentNew will be triggered through the Assign Resource and through the Gantt Chart. The only downside is that I didn't find a way to access the value being changed through the ProjectBeforeAssignmentNew event. You can find the event here.

Related

Show Access Report in dialog

I want to show a Access Report without showing the MainForm from Access. I want to show it in a Dialog or something like that. I can show a report but with opening the Main Window of Access:
_access.DoCmd.OpenReport(
"myReport", //ReportName
AcView.acViewReport, //View
System.Reflection.Missing.Value,
"my Where Condition"
);
But this opens Access all the time. I only want to see my Report in a Dialog. Even when I set the AcWindowMode.acDialog property it opens Access behind it.
I also know that I can save it as PDF and open this. But this is not possible in my case.
Is there a way to show a Access Report in a Dialog without showing (or hiding) the Access Main Window behind it?
You can do it this way:
First, in the access application, set the interface to tabbed. and un-check show tabs.
Now write a code stub in a standard code module in Access.
Say like this:
Sub RunMyReport(s As String)
DoCmd.ShowToolbar "Ribbon", acToolbarNo
' now launch report
DoCmd.OpenReport "qryHotels", acViewReport
End Sub
Now, your .net code will look like this
(I not even bothering with interop - it don't help much).
You have this .net code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim objAccess As Object = CreateObject("Access.Application")
objAccess.OpenCurrentDatabase("C:\test\test44.accdb")
objAccess.Run("RunMyReport", "qryHotels")
objAccess.Visible = True
End Sub
And the results will now look like this:
note the vb form that launched the form.
Note the Access report,
it shows no ribbon or background interface.
And note that we get to pass the report name to the given routine.
Now you likely could move the VBA code of this:
DoCmd.ShowToolbar "Ribbon", acToolbarNo
' now launch report
DoCmd.OpenReport "qryHotels", acViewReport
To the .net side, and not even have to call a VBA routine.
Dim objAccess As Object = CreateObject("Access.Application")
objAccess.OpenCurrentDatabase("C:\test\test44.accdb")
objAccess.DoCmd.ShowToolbar("Ribbon", 2) ' acToolbarNo = 2
objAccess.DoCmd.OpenReport("qryHotels", 5) ' acViewReport = 5
objAccess.Visible = True
So, in fact we don't need the VBA helper function, and we can open any report. The command to hide the ribbon works, and the other settings (hide nav pane, and tabbed interface is to be set in the access application).
So above should work. Given it only a few lines of vb.net code, then as c#, it should be easy to convert. Eg this should work:
{
var objAccess = Interaction.CreateObject("Access.Application");
objAccess.OpenCurrentDatabase(#"C:\test\test44.accdb");
objAccess.DoCmd.ShowToolbar("Ribbon", 2); // acToolbarNo = 2
objAccess.DoCmd.OpenReport("qryHotels", 5); // acViewReport = 5
objAccess.Visible = true;
}
You can consider the inter-op assemblies, as that can help in intel-sense, but above is written without any references to office or use of the inter-op assemblies.

Set value of cell in add in

I will explain everything that I have found but in short I just want to set the value of cell A1 in code behind. I have tried Create Excel Add-in - get cell value plus other links and all those techniques work only if I run them on a macro but I want execute them from a function.
So let me start explaining:
I do not understand why I get a wierd behaviour when I run the same code on a function versus a Sub. Take the following example:
Dim TimeToRun
Sub Macro1()
'Dim addIn As COMAddIn
'Dim automationObject As Object
'Set addIn = Application.COMAddIns("ProbeAddIn")
'Set automationObject = addIn.Object
'automationObject.ImportData
MsgBox "Hello world"
End Sub
Sub Macro2()
TimeToRun = Now + TimeValue("00:00:01")
Application.OnTime TimeToRun, "Macro1" ' run Macro1 on 1 seconds
End Sub
Function Test()
TimeToRun = Now + TimeValue("00:00:01")
Application.OnTime TimeToRun, "Macro1" ' run Macro1 on 1 seconds
End Function
Note that Macro2 and function Test have the same code. Why is it that if I run Macro2 it works ok (message box shows up). But if I go to a a cell and type =Test() then I get an error even though I have the same code !
The reason why am I showing this example is because the code that I have commented out on Macro1 works great if I run it by running the macro directly. If I place that code inside the function Test it does not work. That code is executing the following method in my add-in project on visual studio:
The exception is:
System.Runtime.InteropServices.COMException was unhandled by user code
HResult=-2146827284 Message=Exception from HRESULT: 0x800A03EC
Source="" ErrorCode=-2146827284 StackTrace:
at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes,
MessageData& msgData)
at Microsoft.Office.Interop.Excel.Range.set_Value2(Object value)
at ReadWrite.ImportData() in C:\Users\Antonio\Dropbox_Temp\visual studio\Probe add in
test\ProbeAddIn\ProbeAddIn\Class1.cs:line 82 InnerException:
I got that exception because I ran:
Function Test()
Dim addIn As COMAddIn
Dim automationObject As Object
Set addIn = Application.COMAddIns("ProbeAddIn")
Set automationObject = addIn.Object
automationObject.ImportData
End Function
Instead of
Sub Test()
Dim addIn As COMAddIn
Dim automationObject As Object
Set addIn = Application.COMAddIns("ProbeAddIn")
Set automationObject = addIn.Object
automationObject.ImportData
End Function
How can I make the code inside a macro and function run the same way? I want to trigger the execution of that method when user types in a formula not when the user runs a macro. Even if I have the function run the macro I get the same exception The only way I do not get an exception is if the first thing that runs is a macro...
Not sure, but the issue you have may be due to the fact that when your function is fired the Excel application is in edit mode and so cannot accept any input from outside hence the exception when you try to insert data.
If you want to update a cell in a continuous way in a clean manner what you need is a RTD server.
From your cells you will simply call :
=RTD("my.super.rtdserver",,"mydata")
From time to time your RTD server will notify Excel of new data and lets Excel call-back to retrieve them when it is ready.

Screenshot of VBA editor in debug mode in terminal, or use Excel DOM to find error line and desc in VBA editor?

An Excel workbook is called by a command line, which itself is launched from c# (3.5). The workbook runs, but there is an error in the VBA. For example, a column is missing in a pivot and Excel prompts the user with an error message, with the option to "debug".
From the process in C#, we can detect that the error window is open, and we take a screenshot of the error message, and then we close the error box.
If this was an interactive session, Excel would then present the VBA editor, in debug mode, with the line where the error occurred highlighted.
However, because this is running in an unattended terminal session, we are then unable to take a screenshot of the VBA editor (the screenshot is a black screen).
We are then able to close the excel program, using the windows PID.
The questions is: how do we either get a screenshot of the VBA editor, or how do we bind (with COM or interop) with the Excel in debug mode, and traverse the dom to find the error line, and possibly error message?
(1) If you have authoring control of the Excel workbook(s), you can insert line numbers and a error handler to write to file Err.Number, Err.Description, Err.Source, Erl.
(2) If (1) is not an option, but you can set Macro Security on the host to allow programmatic control of the Visual Basic project, then you can get the active line number:
Dim xl As excel.Application
Dim StartLine As Long, StartColumn As Long, EndLine As Long, EndColumn As Long
Set xl = GetObject(, "Excel.Application")
xl.VBE.ActiveCodePane.GetSelection StartLine, StartColumn, EndLine, EndColumn
Debug.Print StartLine, StartColumn, EndLine, EndColumn
(3) If neither (1) nor (2) is an option, it is a bit hairy, but you can copy out the bitmap contents of the window: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183402%28v=vs.85%29.aspx
The simplest solution if you have access to modify the VBA code might be to use On Error GoTo and a line label. This will stop the Debug message from popping up and instead, when a runtime error occurs, execution will jump to the specified label at which point you can access the information available in the global Err object. VBA does not have the concept of a try/catch block so this is pretty much the nearest proximally.
Sub Main()
On Error GoTo Catch
' Code that may trigger errors here...
GoTo Finally
Catch:
' Log Err info, etc...
Debug.Print "Error " & Err.Number & " - " & Err.Description
Finally:
' This is always reached last (error or no error)
End Sub
Snapshot of data structures, VBA excel
It sounds a lot like the question above in which I suggested to write the state of your code to a separate txt file (in the example, using a FileSystemObject) where you can afterwards analyze where you got stuck:
booleans on conditions / instantiation of objects, number on succesful loops, values contained in variables, well - whatever you want...).
In combination with an error handler (as suggested before) you can see where the code stopped + code / description of the error.
A copy-paste for your sake:
dim sFile As string
Dim FSO As FileSystemObject
Dim FSOFile As TextStream
sFile = "U:/Log.txt"
Set FSO = New FileSystemObject
Set FSOFile = FSO.OpenTextFile(sFile, 2, True)
FSOFile.writeline (<Descriptions+Property/variable value>)
FSOFile.Close set FSO = nothing
I agree that it is a more work, but you'll know where to find the bug.
All depends on how hard and often you need this, in combination with how long the code is that you need to describe. Since I am not aware of your situation, I cannot judge on that.
In the end, it seems a lot of work, but actually it's quite easy since it's just describing the code that is already there.
Hope it helps.

Bind to already opened form created using CreateObject

I was wondering if what I am trying to do is possible. I have created a C# class library which invokes a form when called from VBScript using CreateObject.
I have a VBS that passes some data to the Form and once the script is complete, obviously all references are lost. I wanted to know if there is a way for me to connect and use the existing form the next time I call the VBS script again?
Set e = CreateObject("MyObject")
'SendEvents to Form'
'Script ends.. all references lost'
'Script is run again'
Set e = CreateObject("MyObject")
'Is it possible to send events to the existing form, instead of closing it and creating new one?'
*Edit: Currently, I am using my class lib to close the existing form when the script is called again. However, I have a user request to keep it open regardless of how many times the script is called. I am not sure how I can use the existing form for the next time CreateObject is called. Is it possible?
Try it like this
Set e = CreateObject("MyObject")
'SendEvents to Form'
'Script ends.. all references lost'
'Script is run again'
Set e = GetObject(, "MyObject") 'no, the empty parameter is no typo
See http://technet.microsoft.com/en-us/library/ee176980.aspx for more info.

DirectoryInfo.Delete(True) Doesn't Delete When Folder Structure is Open in Windows Explorer

Assuming I have a folder structure like:
C:\MyTemp
- MySubFolder
If I try to delete this using:
Dim path As String = "C:\MyTemp"
Dim di As System.IO.DirectoryInfo
di = System.IO.Directory.CreateDirectory(path)
di.CreateSubdirectory("MySubFolder")
di.Delete(True)
This works fine, unless I have Windows Explorer open and I'm looking at the 'MySubFolder' directory. Then I get an IOException The directory is not empty. - clicking OK dismisses this and then the folder structure is not deleted.
Any thoughts on how I can get this to perform correctly (i.e. delete), even when running this code while having the folder struture open in Windows Explorer?
Only way you could get this to "work" 100% consistently is by nuking explorer (bad idea) or nuking the handle (also bad idea)
My recommendation would be to just handle the failure gracefully as opposed to trying this.
Check out this article. IOException can be generated from an open handle to the directory: This open handle can result from enumerating directories and files which is exactly what opening in explorer does. Sounds like the actual error message is generic.
The best you can do is catch the error and then use handle.exe to find out which process is using the file and ask the user to close the application with options to retry or cancel.
Ever wondered which program has a particular file or directory open? Now you can find out. Handle is a utility that displays information about open handles for any process in the system. You can use it to see the programs that have a file open, or to see the object types and names of all the handles of a program.
Some more info here:
How to monitor process' IO activity using C#?
I came up with the following DirectoryInfo extension method which wraps the native DirectoryInfo.Delete() method and attempts to "safely delete" the specified folder:
This method requires the following COM reference: Microsoft Internet Controls
x
'''' <summary>
'''' Attempts to perform a "Safe delete" by searching for any Windows File Explorer instances attached to the extended DirectoryInfo Object
'''' and navigate those instances off the current DirectoryInfo path in an attempt to prevent a "Directory is not empty" IOException when
'''' calling DirectoryInfo.Delete(recursive).
'''' </summary>
'''' <param name="DirectoryInfo">The DirectoryInfo object being extended</param>
'''' <param name="recursive">Optional: true to delete this directory, its subdirectories, and all files; otherwise false</param>
'''' <returns>A Boolean indicating whether the DirectoryInfo.Delete(recursive) operation completed without an Exception</returns>
'''' <remarks>Authored by CMC 2013-05-06 12:04:25 PM</remarks>
<System.Runtime.CompilerServices.Extension()> _
Public Function TrySafeDelete(ByVal [DirectoryInfo] As DirectoryInfo, Optional ByVal recursive As Boolean = False, Optional ByVal retryCount As Integer = 0) As Boolean
Const maxRetryCount As Integer = 10
retryCount = If(retryCount < 0, 0, retryCount)
Dim success As Boolean = True
If ([DirectoryInfo] IsNot Nothing) Then
[DirectoryInfo].Refresh()
Dim msWinShellIExplorerWindowsLockingCurrentDirectory As Dictionary(Of SHDocVw.InternetExplorer, DirectoryInfo) = New Dictionary(Of SHDocVw.InternetExplorer, DirectoryInfo)
If ([DirectoryInfo].Exists()) Then
Try
Dim msWinShellIExplorerWindows As SHDocVw.ShellWindows = New SHDocVw.ShellWindows()
For Each msWinShellIExplorerWindow As SHDocVw.InternetExplorer In msWinShellIExplorerWindows
If (msWinShellIExplorerWindow.Name.Equals("windows explorer", StringComparison.OrdinalIgnoreCase)) Then
Dim locationValue As String = msWinShellIExplorerWindow.LocationURL()
If (locationValue.Length() > 0) Then
Dim locationURI As Uri = Nothing
If (Uri.TryCreate(locationValue, UriKind.RelativeOrAbsolute, locationURI)) Then
Dim msWinShellDirectoryInfo As DirectoryInfo = New DirectoryInfo(locationURI.LocalPath())
Dim isLockingCurrentDirectory As Boolean = msWinShellDirectoryInfo.FullName.ToLower().Contains([DirectoryInfo].FullName.ToLower())
If (isLockingCurrentDirectory AndAlso Not msWinShellIExplorerWindowsLockingCurrentDirectory.ContainsKey(msWinShellIExplorerWindow)) Then msWinShellIExplorerWindowsLockingCurrentDirectory.Add(msWinShellIExplorerWindow, msWinShellDirectoryInfo)
End If
End If
End If
Next
Dim navigateCompleteCount As Integer = 0
If (msWinShellIExplorerWindowsLockingCurrentDirectory.Any()) Then
For Each msWinShellDirectoryEntry As KeyValuePair(Of SHDocVw.InternetExplorer, DirectoryInfo) In msWinShellIExplorerWindowsLockingCurrentDirectory
Dim msWinShellIExplorerWindow As SHDocVw.InternetExplorer = msWinShellDirectoryEntry.Key()
Dim msWinShellDirectoryInfo As DirectoryInfo = msWinShellDirectoryEntry.Value()
AddHandler msWinShellIExplorerWindow.NavigateComplete2, New SHDocVw.DWebBrowserEvents2_NavigateComplete2EventHandler(Sub(pDisp As Object, ByRef URL As Object)
navigateCompleteCount += 1
If (navigateCompleteCount.Equals(msWinShellIExplorerWindowsLockingCurrentDirectory.Count())) Then
With [DirectoryInfo]
.Delete(recursive)
.Refresh()
End With
End If
End Sub)
msWinShellIExplorerWindow.Navigate2(New Uri(msWinShellDirectoryInfo.Root.FullName()).AbsoluteUri())
Next
Else
With [DirectoryInfo]
.Delete(recursive)
.Refresh()
End With
End If
Catch ex As Exception
End Try
[DirectoryInfo].Refresh()
If ([DirectoryInfo].Exists() AndAlso (retryCount <= maxRetryCount)) Then
[DirectoryInfo].TrySafeDelete(recursive, retryCount + 1)
End If
success = Not DirectoryInfo.Exists()
End If
End If
Return success
End Function

Categories