I have troubles in Revit API 2022. The Idling Event won't fire when a structural framework instance is selected.
To be more specific, this mysterious error only occurs when a document is updated from a previous revit version to 2022 and the element (structural framework) is already drawn in the document. Any other element category works and if I copy the structural framework or draw a new one it will work too.
My question is, is this a known problem? Is there any workaround or another workflow to get the changed selection?
AddHandler application.Idling, AddressOf application_Idling
'When structural framework is selected it won't fire anymore, if deselected then it will work again
Private Sub application_Idling(sender As Object, e As Autodesk.Revit.UI.Events.IdlingEventArgs)
If uiApplication Is Nothing Then
Return
End If
Dim selected As List(Of ElementId) = uiApplication.ActiveUIDocument.Selection.GetElementIds
'Do Something
End Sub
The Idling event does not fire on selection. You can subscribe to the Idling event to be notified when Revit is not in an active tool or transaction.
Related
I'm updating an existing application that scans barcodes and is written in VB.net running on windows compact framework 3.5. The scanner is a POCKETPC running windows mobile handheld 6.5. I have added code that uses Asynchronous TCP sockets in a class module. The sockets code is reading and sending data to and from a buffer pool. I now need to “inform” the GUI form that data has been received from the TCP socket and is ready for processing. Because the two processes are running on different threads I realise I cannot access the GUI controls directly. I therefore create a windows message (WM_CUSTOMMSG = &H400) and then use “SENDMESSAGE”. There is an existing WndProc sub (Protected Overrides Sub WndProc(ByRef msg As Microsoft.WindowsCE.Forms.Message)) that handles the WM_DECODEDATA for the scanner message. I added in code to now also process the WM_CUSTOMMSG message I am creating. The WM_CUSTOMMSG is arriving at the WndProc and I am able to display a MessageBox and write a log file, but any changes made to the GUI controls just disappear. I tried to start a forms timer but this also has no effect. Code for the WM_DECODEDATA message updates the GUI controls perfectly. What am I missing / done wrong?
Public Class frmHome
Public SockReceiveMsg As Microsoft.WindowsCE.Forms.Message
Public Sub New()
Private yy As Integer = 0
Private xx As Integer = 0
InitializeComponent()
Me.MsgWin = New MsgWindow(Me)
' Add any initialization after the InitializeComponent() call.
SockReceiveMsg = Microsoft.WindowsCE.Forms.Message.Create(MsgWin.Hwnd, MsgWindow.WM_CUSTOMMSG, New IntPtr(xx), New IntPtr(yy))
end class
Private Sub ReceiveCallback(ByVal ar As IAsyncResult)
'This is the async call back sub
MessageWindow.SendMessage(frmHome.SockReceiveMsg)
end sub
Protected Overrides Sub WndProc(ByRef msg As Microsoft.WindowsCE.Forms.Message)
Dim rc As Integer
Dim ar() As String
If msg.Msg = WM_CUSTOMMSG Then
Try
MsgBox("restart timer") 'this displays
Reader.ReaderEngineAPI.Beeper(8, "") 'a quick ok beep. this works
frmHome.timer1.Enabled = False
frmHome.timer1.Interval = 100
frmHome.timer1.Enabled = True
Catch ex As Exception
MsgBox("wndproc Error1: " & ex.Message)
End Try
End If
'pass all messages onto the base processing. Here the windows ones get processed and our ones get cleared and the storage released
MyBase.WndProc(msg)
End Sub
I don't know if/what you're doing wrong, but you can probably do things alot easier. All Controls (including Forms) have an Invoke() method that you can call to let the framework deal with the windows messages.
This article describes it in a bit more detail, including the InvokeRequired property, which you can probably ignore if you know the data is sent from another thread.
If you do choose to handle these messages manually (since you already have the WndProc routine), be sure to catch all exceptions in the method that updates the GUI, and perhaps inspect the InvokeRequired to see if the control agrees that you can update it from that thread.
I would go with a delegate and an eventhandler on the thread code and use InvokeRequired with a custom delegate to update the GUI thread.
Handling custom message is not recommended. Or why does MS hide WndProc in WindowsCE.Forms? OTOH you may need to Refresh the controls that have been changed to let them know that they need to update. Do you use PostMessage or SendMessage. If later, that would block the thread code until the message is processed, which may block the GUI to update itself, if the WndProc itself uses SendMessage inside the code to handle the custom message.
Private Sub UpdateTextBox(ByVal sender As Object, ByVal e As EventArgs)
'---delegate to update the textbox control
txtMessagesArchive.Text += str
End Sub
That would be called from a background thread in the same class via:
Me.Invoke(New EventHandler(AddressOf UpdateTextBox))
Different ways to update GUI from background thread, but in C#
Thank you to all who provided answers. I had previously tried a delegate with invoke but this caused the system to crash. I spent over a week trying to find out what was wrong with the delegate code – I mean it is so simple, but was not successful. I then tried the WndProc route. Josef’s example in his answer showed me that the delegate must be in the same class as the backgound thread. I had placed the delegate in the form class. Once I put the delegate in the correct class it works perfectly. I have 50 years of experience in IT and programming and you can still learn.
I am not going to try to get to the bottom of the WndProc problem. This is now academic as the delegate route is a better solution and is now working. Thank you
I'm using vb.net 2013, and I have configured Shutdown mode to "When last form is closed".
On my main form, I have a menu item which has this code to close the application:
Application.Exit
Everything is working fine, except one case:
When I open a specific form, where a Combobox is created on runtime and I've used Addhandler to subscribe to several events.
The combobox is created when pressing a button.
When I open this form and I don't create the combobox, everything is working ok. If the combobox is created, when I close this form and try to close the application using the menu item, nothing happens. The application is not closed and no error message is displayed. (the same situation occurs when I try to close the main form with "x" button)
On the form's (where I have the combobox) close event , I tried to put a line of code:
MyCombobox.dispose()
But the situation is the same.
What can I do? Thank you!
What I do from my little experience is
1.) remove the MyCombobox from its parent control (I'm think this is in your combobox close event).
2.) set the MyCombobox to Nothing
3.) Dispose() it.
MyCombobox = Nothing
MyCombobox.Dispose()
It would be useful to see some part of your code for the close event so we can help you check. More power to you!
Update based on OP's comment:
I have read the following from MSDN: https://msdn.microsoft.com/en-us/library/system.windows.forms.form.closing(v=vs.110).aspx
From this, it is important to note that:
The Form.Closed and Form.Closing events are not raised when the Application.Exit method is called to exit your application. If you have validation code in either of these events that must be executed, you should call the Form.Close method for each open form individually before calling the Exit method.
I have an application written in VB.net. In that application I have multiple forms and lots of functionality. The form the application starts with, is some kind of menu. In the background, I have a list of menu items that a user can see and use to open a new form. It is possible to search through all those menu items via a textbox where you can fill in some text and the code then compares all the menu items names to the filled in text and shows the result. This event is fired on every textchanged event of this textbox. But if the user types in a name that occures a lot (like à 100 times or so) the view takes some time (3 to 5 sec) to display all those results. Now I would like to know if it is possible to abort the first event handler if the same event is called again. That means that if I am typing in the textbox and for the first 4 or 5 letters almost all menu items are matches, so I want to abort that search and start a new one right away. Is there any way to detect that the same event is called again and abort the currect one to make the new one start right away?
Thanks in advance for reading this and helping me solve this problem!
In order to accomplish this, you are going to have to do the work in a separate thread. The primary reason for that is that WinForms are single-threaded. All UI-related events in a WinForm are handled on the same UI thread. As such, there is no way for the TextChanged event to fire again while you are still in the middle of processing the previous event. The UI will be locked up until the first event if finished processing.
However, if you do all of the menu-filtering work in another thread, then your UI will be freed-up to react to user input while you are doing the work. Then your TextChanged event will be allowed to fire before the previous one is done processing.
The easiest way to implement multi-threading in a WinForm project is to use the BackgroundWorker component. You can find it in the form-designer tool box. Luckily, the BackgroundWorker component has some properties and methods which are useful for implementing the cancellation as you described.
For instance, here's a very simple example. In this example, every time the text in TextBox is changed, it starts BackgroundWorker1 performing some work. The work that it does is to simply wait two seconds and then copy the contents of TextBox1 to TextBox2. If the text changes again before those two seconds are complete, it cancels the bacground work and starts it again from the beginning.
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
If BackgroundWorker1.IsBusy Then
BackgroundWorker1.CancelAsync()
Else
BackgroundWorker1.RunWorkerAsync()
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For i As Integer = 1 To 20
If BackgroundWorker1.CancellationPending Then
e.Cancel = True
Exit Sub
End If
Thread.Sleep(100)
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If e.Cancelled Then
BackgroundWorker1.RunWorkerAsync()
Else
TextBox2.Text = TextBox1.Text
End If
End Sub
In order for the above example to work, the BackgroundWorker1.WorkerSupportsCancellation property must be set to True.
As you can see, when the text changes, it simply checks the IsBusy property, which determines whether or not the background thread is still working from a previous event. If it is, it cancels it. If it's not, it starts it.
All of the work which needs to be done on the separate thread is done inside the background worker's DoWork event handler. As it is doing the work, it needs to periodically check whether or not it has been canceled. If it has been canceled, it needs to stop what it's doing and set the Cancel property of the event args to indicate that it is stopping because it was canceled.
Once the background work is done, (whether by cancellation or by completing its task), the background worker raises the RunWorkerCompleted event. The event args for that event have a Cancelled property which indicates whether or not the work completed because it had been canceled prematurely. In the example, if it was canceled, it simply restarts the work from the beginning.
For what it's worth, all of this would be moot if there was some way for you to speed up the menu-filtering algorithm to the point where it's near instantaneous. It may be possible to do that by indexing your menus in something like a suffix array.
Try to add a condition. In your search method, if your text length exceeds some value, call the same event again (like in recursive methods). It should works.
Regards,
Daniel
add some condition on length of search string...like on its Length should be 5 or more
OR maximum results shown at one time should be limited.
How can you add a click event to dynamically created menu item?
I thought I could do something like
Loop through all the items in the Menu1.DropDownItems then create a mousedown even on the item and execute an action based off that.
I'm new to VB and was wondering what logic to use. Will that even work? How will the events be saved through the life of the application?
You will have to have a method that fits the event's signature. Then, when creating the MenuItem, you can add a handler to the event:
Dim item As New MenuItem(...)
'...
AddHandler item.Click, AddressOf myEventHandler
Sub myEventHandler(sender As Object, e As System.EventArgs)
'Do something
End Sub
You cannot create an event in a class that you don't have access to. The only option would be to derive from it, but this works only in some cases. In case of the MenuItem, this is not even necessary, because it already provides a Click event. You just have to add a handler for it. The handler is saved in the item's event, which maintains a kind of list of handlers.
I have a custom datagridviewcolumn in which i've added an event.
The problem is, I can't work out how to see who has subscribed to the current column object's event and add those subscriptions to the cloned column object.
I get around this problem by asking the calling program to pass the address of the handler to a delegate in the custom column, instead of adding a handler to the event.
Forgive my terminology, I hope you understand what i'm trying to say!
By receiving the reference to the method, the datagridviewcolumn now has control and can then easily clone that reference.
This is fine, but users of controls expect to be able to suscribe to events by selecting the event in visual studio - which creates a template of the method.
At least in C# you can have "adders" and "removers" for events, like getters and setters for properties.
Maybe you can use that to do some custom processing during the process of somebody adding an event handler to the event?
EDIT
I don't know much about VB.NET, but I googled a little and found the following snippet:
Public Delegate Sub WorkDone(ByVal completedWork As Integer )
Private handlers As New ArrayList()
Public Custom Event WorkCompleted As WorkDone
AddHandler (ByVal value As WorkDone)
If handlers.Count <= 5 Then
handlers.Add(value)
End If
End AddHandler
RemoveHandler(ByVal value As WorkDone)
handlers.Remove(value)
End RemoveHandler
RaiseEvent (ByVal completedWork As Integer)
If completedWork > 50 Then
For Each handler As WorkDone In handlers
handler.Invoke(completedWork)
Next
End If
End RaiseEvent
End Event
This should help you customize your event handler, so that you can "see" the delegates being added to the event from within your class.
For anyone interested, I chanced upon a way to perform this without the need for a custom event.
By suffixing the event name with Event, you have access to some properties and methods.
So in Thorsten's example, I would refer to the event as WorkCompletedEvent.
Specific to this question, there is the method GetInvocationList that returns a list of delegates attached to the event.
In addition to this, checking if WorkCompletedEvent IsNot Nothing, will tell you whether there are handlers for the event without having to retrieve the invocation list.