Author Topic: 2-3 connected questions: openclosetransaction, redo, Xrecord  (Read 2380 times)

0 Members and 1 Guest are viewing this topic.

nekitip

  • Guest
2-3 connected questions: openclosetransaction, redo, Xrecord
« on: April 29, 2013, 05:04:51 AM »
I have always had a problem for not being able to see REDO in autocad in my app, never realy undersood the problem and what's causing it.
Hovever, there is a blog:
http://adndevblog.typepad.com/autocad/2012/06/nothing-to-redo-message-appears-straight-after-undoing-in-autocad-for-net-and-objectarx.html
It explains that NO TRANSACTION sould be start from practicaly anywhere after UNDO, or it will ruin REDO. But you must react in some way, this is an event driven envirement, so it suggests OpenCloseTransaction. It works, but...

If the transaction is classic, then code works, undo works, but not redo.
If the transaction is openclose, then code works, undo works, redo works, but ent.Database.DisableUndoRecording(True) does not!
The idea behind disableundorecording is that i have one transaction that writes to 10 000 objects, and some of them do not have XRecord, wich i add when needed. But, if i do UNDO, than Xrecord is destroyed also, not just its data, and that is not accepteble, so i disable it when needed for some object.
Also, in the following partial code, only during REDO - the undo hander is called 3 times in a row, with no reason obvius to me. Is that normal?

It can all be duck-taped, but it is not clean, and I am sure that my notunderstanding of openclosetransaction is causing it.
1) So, how to react to "command ended" and not trash REDO buffer?
2) why is handler called 3 times?
3) And how not to delete Xrecord in UNDO but only it's data.

partial code
Code - Visual Basic: [Select]
  1. 'partial code!!!!
  2. '
  3.     AddHandler obj.ModifyUndone, AddressOf handler_objUndone
  4.      
  5.     Private Sub handler_objUndone(ByVal o As Object, ByVal e As EventArgs)
  6.         ''''''''''''''''''''''''''''''''''''''''''''''''''''
  7.        'one event is normal, but 3 events fired here after REDO
  8.        ''''''''''''''''''''''''''''''''''''''''''''''''''''
  9.    End Sub
  10.  
  11.     Private Function createXrecForEntity(ByVal ent As Entity, ByRef trans As Transaction) As ObjectId
  12.         ent.Database.DisableUndoRecording(True)
  13.         Dim retval As New ObjectId
  14.         If ent.ExtensionDictionary.IsNull Then
  15.             ent.UpgradeOpen()
  16.             ent.CreateExtensionDictionary()
  17.         End If
  18.         Dim extensionDict As DBDictionary = trans.GetObject(ent.ExtensionDictionary(), OpenMode.ForWrite)
  19.         Using newxrec As New Xrecord
  20.             extensionDict.SetAt(_name, newxrec)
  21.             trans.AddNewlyCreatedDBObject(newxrec, True)
  22.             retval = newxrec.ObjectId
  23.         End Using
  24.         ent.Database.DisableUndoRecording(False)
  25.         Return retval
  26.     End Function
  27.  


TheMaster

  • Guest
Re: 2-3 connected questions: openclosetransaction, redo, Xrecord
« Reply #1 on: April 29, 2013, 02:50:53 PM »
I have always had a problem for not being able to see REDO in autocad in my app, never realy undersood the problem and what's causing it.
Hovever, there is a blog:
http://adndevblog.typepad.com/autocad/2012/06/nothing-to-redo-message-appears-straight-after-undoing-in-autocad-for-net-and-objectarx.html
It explains that NO TRANSACTION sould be start from practicaly anywhere after UNDO, or it will ruin REDO. But you must react in some way, this is an event driven envirement, so it suggests OpenCloseTransaction. It works, but...

If the transaction is classic, then code works, undo works, but not redo.
If the transaction is openclose, then code works, undo works, redo works, but ent.Database.DisableUndoRecording(True) does not!
The idea behind disableundorecording is that i have one transaction that writes to 10 000 objects, and some of them do not have XRecord, wich i add when needed. But, if i do UNDO, than Xrecord is destroyed also, not just its data, and that is not accepteble, so i disable it when needed for some object.
Also, in the following partial code, only during REDO - the undo hander is called 3 times in a row, with no reason obvius to me. Is that normal?

It can all be duck-taped, but it is not clean, and I am sure that my notunderstanding of openclosetransaction is causing it.
1) So, how to react to "command ended" and not trash REDO buffer?
2) why is handler called 3 times?
3) And how not to delete Xrecord in UNDO but only it's data.

partial code
Code - Visual Basic: [Select]
  1. 'partial code!!!!
  2. '
  3.     AddHandler obj.ModifyUndone, AddressOf handler_objUndone
  4.      
  5.     Private Sub handler_objUndone(ByVal o As Object, ByVal e As EventArgs)
  6.         ''''''''''''''''''''''''''''''''''''''''''''''''''''
  7.        'one event is normal, but 3 events fired here after REDO
  8.        ''''''''''''''''''''''''''''''''''''''''''''''''''''
  9.    End Sub
  10.  
  11.     Private Function createXrecForEntity(ByVal ent As Entity, ByRef trans As Transaction) As ObjectId
  12.         ent.Database.DisableUndoRecording(True)
  13.         Dim retval As New ObjectId
  14.         If ent.ExtensionDictionary.IsNull Then
  15.             ent.UpgradeOpen()
  16.             ent.CreateExtensionDictionary()
  17.         End If
  18.         Dim extensionDict As DBDictionary = trans.GetObject(ent.ExtensionDictionary(), OpenMode.ForWrite)
  19.         Using newxrec As New Xrecord
  20.             extensionDict.SetAt(_name, newxrec)
  21.             trans.AddNewlyCreatedDBObject(newxrec, True)
  22.             retval = newxrec.ObjectId
  23.         End Using
  24.         ent.Database.DisableUndoRecording(False)
  25.         Return retval
  26.     End Function
  27.  

Doing things in a 'command ended' reactor is a bad idea, because there are many ways that an object can be modified without starting/ending a command.

You don't show enough code to tell definitively, but it may be possible to  use ObjectOverrule to solve the problem more cleanly and universally, without the undo/redo problem.

nekitip

  • Guest
Re: 2-3 connected questions: openclosetransaction, redo, Xrecord
« Reply #2 on: April 30, 2013, 04:55:08 AM »
so here is complete code for create xrecord in provided Entity and to use it to store data of custom type (like your own class)
using:
1) create instance in your class with your type of data... dim myxrec as acXrec(of mydatastorageclass)
2) add handler for undoing event isUndoingEvent (and remember to remove it later). test for really isUndoing (if you have more of this), and read values, set isUndoing=false
3) call SaveDataToXrec or SaveStreamToXrec to save it, GetDataFromXrec to read.... remember to check for "nothing" when object is returned


Code - vb.net: [Select]
  1. Public Class acXrec(Of T)
  2.  
  3.     Private oid As ObjectId
  4.     Public oidIsConnectedToAcad As Boolean
  5.     Private _name As String = "unknown"
  6.     Private _isundoing As Boolean
  7.  
  8.     Public Event isUndoingEvent()
  9.  
  10.     Public ReadOnly Property nameXrecord As String
  11.         Get
  12.             Return _name
  13.         End Get
  14.     End Property
  15.  
  16.     Public Property isUndoing As Boolean
  17.         Get
  18.             Return _isundoing
  19.         End Get
  20.         Set(ByVal value As Boolean)
  21.             _isundoing = value
  22.             If value = True Then RaiseEvent isUndoingEvent()
  23.         End Set
  24.     End Property
  25.  
  26.     Private Sub reg(ByVal trans As Transaction, ByVal on_off As Boolean)
  27.         'If oid.IsValid Then
  28.         Dim obj As DBObject = trans.GetObject(oid, OpenMode.ForRead, True)
  29.         Select Case on_off
  30.             Case True
  31.                 If oidIsConnectedToAcad = False Then
  32.                     AddHandler obj.ModifyUndone, AddressOf handler_objUndone
  33.                     oidIsConnectedToAcad = True
  34.                 End If
  35.             Case False
  36.                 If oidIsConnectedToAcad = True Then
  37.                     RemoveHandler obj.ModifyUndone, AddressOf handler_objUndone
  38.                     oidIsConnectedToAcad = False
  39.                 End If
  40.         End Select
  41.     End Sub
  42.  
  43.     Public Sub disconnect_Xrecord(ByVal trans As Transaction)
  44.         If oidIsConnectedToAcad Then
  45.             register_oid_events(trans, False)
  46.         End If
  47.     End Sub
  48.  
  49.     Private Sub handler_objUndone(ByVal o As Object, ByVal e As EventArgs)
  50.             ''''''''''''''''''''''''''''''''''''''''''''''''''''
  51.         'uknown 3 event firering after REDO, otherwise 1
  52.         ''''''''''''''''''''''''''''''''''''''''''''''''''''
  53.         Dim dbo As DBObject = TryCast(o, DBObject)
  54.         If isUndoing = False Then isUndoing = True 'dbo.IsUndoing
  55.     End Sub
  56.  
  57.     Public Sub connect_Xrecord(ByVal owners_oid As ObjectId, ByVal trans As Transaction, ByVal forceNewIfNotExists As Boolean, ByVal newname As String)
  58.         If oidIsConnectedToAcad = False Then
  59.             _name = newname
  60.             Dim ent As Entity = trans.GetObject(owners_oid, OpenMode.ForRead)
  61.             Dim novioid As ObjectId = checkIfExists(ent, trans)
  62.  
  63.             If novioid.IsValid Then
  64.                 oid = novioid
  65.                 reg(trans, True)
  66.             Else
  67.                 If forceNewIfNotExists Then
  68.                     oid = createXrecForEntity(ent, trans)
  69.                     register_oid_events(trans, True)
  70.                 End If
  71.             End If
  72.         End If
  73.     End Sub
  74.  
  75.     Private Function checkIfExists(ByVal ent As Entity, ByRef trans As Transaction) As ObjectId
  76.         Dim retval As New ObjectId
  77.         If ent.ExtensionDictionary.IsValid Then
  78.             Dim extensionDict As DBDictionary = trans.GetObject(ent.ExtensionDictionary(), OpenMode.ForRead)
  79.             If extensionDict.Contains(_name) Then
  80.                 Dim xRec As Xrecord = trans.GetObject(extensionDict.GetAt(_name), OpenMode.ForRead)
  81.                 retval = xRec.ObjectId
  82.             End If
  83.         End If
  84.         Return retval
  85.     End Function
  86.  
  87.     Private Function createXrecForEntity(ByVal ent As Entity, ByRef trans As Transaction) As ObjectId
  88.         ent.Database.DisableUndoRecording(True)
  89.         Dim retval As New ObjectId
  90.         If ent.ExtensionDictionary.IsNull Then
  91.              ent.UpgradeOpen()
  92.             ent.CreateExtensionDictionary()
  93.         End If
  94.         Dim extensionDict As DBDictionary = trans.GetObject(ent.ExtensionDictionary(), OpenMode.ForWrite)
  95.         Using newxrec As New Xrecord
  96.             extensionDict.SetAt(_name, newxrec)
  97.             trans.AddNewlyCreatedDBObject(newxrec, True)
  98.             retval = newxrec.ObjectId
  99.         End Using
  100.         ent.Database.DisableUndoRecording(False)
  101.         Return retval
  102.     End Function
  103.  
  104. #Region "XrecSaveRead of T"
  105.  
  106.     Public Sub SaveDataToXrec(ByVal dataobject As T, ByVal trans As Transaction)
  107.         SaveStreamToXrec(DataToStream(dataobject), trans)
  108.     End Sub
  109.  
  110.     Public Function GetDataFromXrec(ByVal trans As Transaction) As T
  111.         Dim retval As T
  112.         If oidIsConnectedToAcad Then
  113.             Dim xs As New XmlSerializer(GetType(T))
  114.             Dim ms As MemoryStream = ReadStreamFromXrec(trans)
  115.             If ms.Length > 0 Then retval = xs.Deserialize(ms)
  116.         End If
  117.         Return retval
  118.     End Function
  119.  
  120.     Private Function DataToStream(ByVal objectToSerializate As T) As MemoryStream
  121.         Dim ms As New MemoryStream
  122.         Dim xs As New XmlSerializer(GetType(T))
  123.         xs.Serialize(ms, objectToSerializate)
  124.         ms.Position = 0
  125.         Return ms
  126.     End Function
  127. #End Region
  128.  
  129. #Region "Xrec stream"
  130.     Public Sub SaveStreamToXrec(ByVal memstream As MemoryStream, ByVal trans As Transaction)
  131.         If oidIsConnectedToAcad = True Then
  132.             Dim ent As Xrecord = trans.GetObject(oid, OpenMode.ForWrite)
  133.             ent.Data = tools.StreamToResBuf(memstream)
  134.         End If
  135.     End Sub
  136.     Public Function ReadStreamFromXrec(ByVal trans As Transaction) As MemoryStream
  137.         Dim ms As New MemoryStream
  138.         If oidIsConnectedToAcad = True Then
  139.             Dim ent As Xrecord = trans.GetObject(oid, OpenMode.ForRead)
  140.             Dim xbuff As ResultBuffer = Nothing
  141.             xbuff = ent.Data
  142.             If xbuff IsNot Nothing Then ms = tools.ResBufToStream(xbuff)
  143.             Me.isUndoing = False
  144.         End If
  145.         Return ms
  146.     End Function
  147. #End Region
  148. End Class
  149.  
  150.  
Also, you need something to chunk data to 127 chunks, so put somewhere else this tool/helper. You can google out several ideas of this. I used this one, it was good, but i recently abandon it for a different, however this version is much easyer to follow basic concept.
Code - vb.net: [Select]
  1. #Region "memory stream chunker"
  2.     Public Shared Function StreamToResBuf(ByVal ms As MemoryStream) As ResultBuffer
  3.         Const kMaxChunkSize As Integer = 127
  4.         Dim resBuf As New ResultBuffer 'if needed, write app name. (New TypedValue(CInt(DxfCode.ExtendedDataRegAppName), appName))
  5.         Dim i As Integer = 0
  6.         While i < ms.Length
  7.             Dim length As Integer = CInt(Math.Min(ms.Length - i, kMaxChunkSize))
  8.             Dim datachunk As Byte() = New Byte(length - 1) {}
  9.             ms.Read(datachunk, 0, length)
  10.             resBuf.Add(New TypedValue(CInt(DxfCode.ExtendedDataBinaryChunk), datachunk))
  11.             i += kMaxChunkSize
  12.         End While
  13.         Return resBuf
  14.     End Function
  15.     Public Shared Function ResBufToStream(ByVal resBuf As ResultBuffer) As MemoryStream
  16.         Dim ms As New MemoryStream()
  17.         Dim values As TypedValue() = resBuf.AsArray()
  18.         ' Start from 1 to skip application name, or 0 if no appname is written
  19.         For i As Integer = 0 To values.Length - 1
  20.             Dim datachunk As Byte() = DirectCast(values(i).Value, Byte())
  21.             ms.Write(datachunk, 0, datachunk.Length)
  22.         Next
  23.         ms.Position = 0
  24.         Return ms
  25.     End Function
  26. #End Region
  27.  

I think it is a good one and hope it will be usefull to someone as others code was usefull to me. There is a room for making it better.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
However, my 3 questions from the begining this topic remains!
To Tony. My programing idea in general is that each of my Entitys have DBobjmodified event connected. When it fires, it adds that ObjectIDs into a list for later. When that list notice "command ended" it opens single transaction and iterates. There is no other way I can think of to start group transaction. "Application.idle" is other way list can be started, but in my case irrelevant - (command will ended anyway)
Have any other conceptual idea?
I must admit that i haven't ever tried to use ObjectOverrule... So i will look into it.

TheMaster

  • Guest
Re: 2-3 connected questions: openclosetransaction, redo, Xrecord
« Reply #3 on: April 30, 2013, 09:48:13 PM »
Search here for the thread "Can you break this?", and you'll see an example of an ObjectOverrule that would very easily solve the problem without all of the event handlers, command-ended  business.

ObjectOverrule is a much more direct and straight-forward solution.

nekitip

  • Guest
Re: 2-3 connected questions: openclosetransaction, redo, Xrecord
« Reply #4 on: May 02, 2013, 04:39:10 AM »
Thank you for your anwser. Well the first thing I noticed in that post was:
Code - C#: [Select]
  1. tr = blkref.Database.TransactionManager.TopTransaction;
Shame I never really had idea what is it good for before.
It is not fair that official help, and "first app" type of blogs are not emphasizing this.