RPS, windows Form & Revit API (Python) - winforms

I've been trying to build a form to create and delete Revit print Sets.
I've 2 main issues:
1) I'm able to create a print set but I cannot access its content unless I restart the Form. I get the errors below (depending if I'm defining the view_set variable or not)
List_object_has_no_attribute_Views
Local_variable_referenced_before_assignment
This is the code of the function to display the sheets of the selected Print Set
def DisplaySheetsInSet (self, sender, args):
self.curItem = CurrentSetsListBox.SelectedItem
PrintSetForm_Load
try:
view_set=[]
for i in PrintSetForm.ViewSets:
if i.Name == str(self.curItem):
view_set = i
else:
continue
Sheets=[sheet.Name for sheet in view_set.Views]
SheetsLb.BeginUpdate()
SheetsLb.Items.Clear()
for sheet in Sheets:
SheetsLb.Items.Add(sheet)
SheetsLb.EndUpdate()
except Exception as e:
popup (str(e)
2) I'm able to delete print sets once. If I try do delete another one I get the following error and I need to restart the form ( code for the function that deletes the print sets shown below)
The_referenced_object_is_not_valid
def DelPrintSet(self, sender, args):
self.curItem = CurrentSetsListBox.SelectedItems
t = Transaction (doc, 'Delete printset')
t.Start()
for viewset in PrintSetForm.ViewSets:
if viewset.Name in [str(item) for item in self.curItem]:
doc.Delete(viewset.Id)
doc.Regenerate()
else:
continue
self.Refresh()
UpdateSetNames(CurrentSetsListBox)
t.Commit()
I've tried to build a function to restart/refresh the Form but it doesn't work (code below):
global PrintSetForm_Load
def PrintSetForm_Load(self, sender):
Application.Exit()
Application.Restart()
#self.Refresh()
#self.ResetBindings()
#self.ActiveForm.Close()
sd = PrintSetForm()
sd.ShowDialog()
This gif shows the form in action:
Manage Print Sets
Any ideas or suggestions?
Thank you.
3) If I try to populate the SheetsLb with a DataSource, just the first set clicked is shown.
Sheets=[sheet.Name for sheet in view_set.Views]
SheetNumber=[sheet.get_Parameter(BuiltInParameter.SHEET_NUMBER).AsString() for sheet in view_set.Views]
SheetsLb.BeginUpdate()
SheetsLb.DataSource = None
SheetsLb.Items.Clear()
UpdatedList=[]
for number,name in zip(SheetNumber,Sheets):
UpdatedList.append(number+" - "+ name + " [ ] ")
SheetsLb.DataSource=UpdatedList
SheetsLb.EndUpdate()

1) See if this works:
It would be worth checking that there is something selected in self.viewSetsLb. Ive added a check to the code below
The view_set variable could be initialised as a boolean instead of a list
Using break in the for loop keeps things a little snappier
Ive used the more pythonic for view in PrintSetForm.viewSets rather than for i in PrintSetForm.viewSets - keeping it nice and clear
This code works for me:
self.curItem = self.viewSetsLb.SelectedItem
if not self.viewSetsLb.SelectedItem:
print 'No Printset selected!'
return
view_set = False
for view in PrintSetForm.viewSets:
if view.Name == str(self.curItem):
view_set = view
break
else:
continue
Sheets=[sheet.Name for sheet in view_set.Views]
self.sheetsLb.BeginUpdate()
self.sheetsLb.Items.Clear()
for sheet in Sheets:
self.sheetsLb.Items.Add(sheet)
self.sheetsLb.EndUpdate()
2) Its because the data in your PrintSetForm.ViewSets list is out of date. Every time you change something (ie delete a viewset), repopulate this list:
PrintSetForm.ViewSets = FilteredElementCollector(doc).OfClass(ViewSheetSet).ToElements()
Also, you shouldnt need to build a refresh button, perhaps have a class function that repopulates the Printset list and ListBox, and clears the Sheet ListBox that you call after every action?
Sounds like youre having fun mate!

It sounds as if you have an issue with the scoping and lifetime of variables. For instance, some variables may have a lifetime limited to the form display, and therefore cannot be accessed after the form is closed. You could change the lifetime of these variables, e.g., by making them static class variables instead of local instance variables. I suggest you read up on .net static class variable scope.

Related

Concurrency Error WinForms Binding Source Navigator

I have a form with customer info that needs to be processed one transaction per page. I'm using the binding navigator to manage my pagination.
It works in all but some cases. In the cases where it doesn't work, I have to open a different window to look up information and return it to the main form. Here is the code for that:
// save current work
updateDataTable();
// Open a window and get new customer info
// CurrentCustomer is returned from the opened window
using (SqlConnection cx = new SqlConnection(GetConnectionString()))
{
DataRowView dataRow = (DataRowView)procBindingSource.Current;
dataRow.BeginEdit();
dataRow["CUSTOMER"] = CurrentCustomer;
dataRow.EndEdit();
updateDataItems();
SqlCommand cmd = new SqlCommand(
#" select acct_no from cust_processing where id = #id ", cx);
cmd.Parameters.AddWithValue("#id", (int)dataRow["ID"]);
cx.Open();
var results = cmd.ExecuteScalar();
if (results != null)
{
dataRow.BeginEdit();
dataRow["ACCT_NO"] = results.ToString();
dataRow.EndEdit();
updateDataItems(); <------ CONCURRENCY ERROR
}
}
The error I am getting is a concurrency error. I think that I have more than one version of the row possibly ? I thought I was making sure that I was on the most recent version of the row by calling updateDataTable(). I am the only user so I know I am creating the problem myself.
Here is my update method which is called when I change pages or save and exit or want to write the commit the data:
void updateDataItems()
{
this.procBindingSource.EndEdit();
this.procTableAdapter.Update(xyzDataSet);
xyzDataSet.AcceptChanges();
}
I have tried executing updateDataItems from various places such as after I assign dataRow["ACCT_NO"] = results.ToString() or before and after assigning that.
I'm pretty much down to guess and check so any thoughts, help and advice will be appreciated and +1.
Okay -- so the problem was that I was trying to update the current row from the program and also using the binding navigator. They were not working together properly.
The solution was to add a text box to the form in the forms designer and set visible = false and bind it to ACCT_NO. Once I got the results from my other form, I just needed to set the .text property of the ACCT_NO textbox to the new value and the binding navigator managed all my updates for me correctly.
txtAcct_No.text = results.ToString();

How to make a button API

I am trying to make an easy to use button API in Lua with ComputerCraft, and I'm having some trouble. When I do:
os.loadAPI("button")
action=function()
term.clear()
term.setCursorPos(1,1)
print("Hello!")
end
button.newButton("B1",5,5,20,10)
button.drawButton("B1",colors.orange,colors.white)
button.onClick("B1",action,true)
Nothing happens, it doesn't even draw the colors. I have done tests, and when I store something like colors.white as a variable, then print the variable, it returns the number code of that color, which comes from the colors API. Here is what I have:
--to use the newButton function, do this:
--button.newButton(exampleButton)
--to use onClick function, create a variable like this:
--exampleFunc=function()
--(code)
--end
--Then call onClick with the same variable:
--button.onClick(exampleButton,exampleFunc)
buttons={}
xPos=0
yPos=0
function removeButton(buttonName)
for key, fields in pairs(buttons) do
if key == buttonName then
table.remove(button,buttonName)
else
print("ERROR: button name not available")
end
end
end
function onClick(buttonName,action,boolean)
for key, fields in pairs(buttons) do
if boolean then
testClick(action)
end
end
end
function drawSeparateButton(x,y,w,h,outLineColor,fillColor)
if key == buttonName then
x=buttons[buttonName]["x"]
y=buttons[buttonName]["y"]
w=buttons[buttonName]["w"]
h=buttons[buttonName]["h"]
paintutils.drawBox(x,y,x+(w-1),y+(h-1),outLineColor)
paintutils.drawFilledBox(x+1,y+1,x+(w-2),y+(h-2),fillColor)
end
end
function testClick(action)
for key, fields in ipairs(buttons) do
x=buttons[buttonName]["x"]
y=buttons[buttonName]["y"]
w=buttons[buttonName]["w"]
h=buttons[buttonName]["h"]
x2=x+(w-1)
y2=y+(h-1)
button,xPos,yPos=os.pullEvent("mouse_click")
if xPos>=x and xPos<=x2 and yPos>=y and yPos<=y2 then
action()
end
end
end
function newButton(buttonName,X,Y,W,H)
buttons[buttonName] = {x=X,y=Y,w=W,h=H}
end
function drawButton(buttonName,outLineColor,fillColor)
for key, fields in ipairs(buttons) do
if key == buttonName then
x=buttons[buttonName]["x"]
y=buttons[buttonName]["y"]
w=buttons[buttonName]["w"]
h=buttons[buttonName]["h"]
x2=x+w-1
y2=y+h-1
x3=x+1
y3=y+1
x4=x+w-2
y4=y+h-2
paintutils.drawBox(x,y,x2,y2,outLineColor)
paintutils.drawFilledBox(x3,y3,x4,y4,fillColor)
elseif key ~= buttonName then
print("Button name not availabel")
end
end
end
I just need to be able to store a color like colors.white in a variable and have it returned as colors.white, and not the color code. I also need to be able to check which button is clicked and run a function specified by the user when one of the buttons are clicked.
I'm going to walk through your prototype code and point out some errors I see and also try to answer your question. I'm going to assume that you want to set a key value in a table to an array and access that externally.
A quick and short answer to your question is that you can store tables within tables and access them through keys or indices. A design change I would make, however, is to store your exampleFunc as a member of each button table to associate it with a specific button.
Example:
buttons = {}
buttons.playButton = {x=0, y=0, w=10, h=10, func=function() return end}
buttons.quitButton = {x=0, y=30, w=10, h=10, func=function() return end}
...
buttons.quitButton.x = 10
buttons.playButton.func()
Tables have a key-value structure, where keys can be strings or numbers. There are multiple ways to access an array using a key depending on the data type of the key.
For example, instead of writing buttons.quitButton.x = 10 we could've written buttons["quitButton"].x = 10 or buttons["quitButton"]["x"] = 10 or buttons.quitButton["x"] = 10.
This page is a great starting point for learning about Lua's tables.
According to this page, os.pullEvent() is blocking, and you will only be able to check if one button is clicked per mouse click. Consider looping through your buttons table and checking every button to see if the mouse falls within its rectangular bounds. Once you find which button the mouse clicked, you can call its func member. While we're still discussing this method, the while true do loop is completely unnecessary.
function removeButton(buttonName)
for buttonName in pairs(button) do
...
function newButton(buttonName)
state=true
for buttonName in pairs(buttons) do
...
You may come from a Python background where the statement if element in list exists but Lua has no such statement. The for loops you use are looping through every member of the list. You also aren't capturing all of the variables returned by the pairs() function. A fix for that would look something like the following:
function buttonFunction(buttonName)
for key, fields in pairs(buttons) do
if key == buttonName then
...
end
end
There are multiple instances where you refer to the variable button when you mean buttons.

Null reference when using a variable with findcontrol

I have a .aspx page with several textboxes, including textboxes with IDs of txtID1, txtID2, txtID3... and so on.
I am attempting populate the textboxes with a data from an XML file by looping through a node list. With each loop, I want to use the FindControl method to locate txtID1 and set its .Text to the value of the id attribute of the first node; then locate txtID2 and its .Text to the value of the id attribute of the second node, and so on.
When the following line of code is run, I get a null reference error for TextBox txtID, so it appears that I am doing something wrong with the FindControl method. Is my syntax incorrect? Do I need to use a different method?
int x = 1;
XmlNodeList getAuthors = getItem.SelectNodes("item/authors");
foreach (XmlNode getAuthor in getAuthors)
{
TextBox txtID = (TextBox)Page.FindControl("txtID" + x.ToString());
txtID.Text = getAuthor.Attributes["id"].Value.ToString();
x = x + 1;
}
After further research (that is, lots of Googling), it appears that I may be running into this problem because I am using a master page. Neither (TextBox)FindControl nor (TextBox)Page.FindControl was working so I have abandoned this approach. Here is an old article that seems to explain my problem.
http://weblog.west-wind.com/posts/2006/Apr/09/ASPNET-20-MasterPages-and-FindControl

TableAdapter.Update Does Not Save Imported Row

I've hit a problem with my database. The purpose of the section of my application is simple this; when a user marks an assignment of work complete, it will transfer one DataRow from "activeUnits" by using the "table.ImportRow" method to another table called "completedUnits" and then use the table adapter to update this change to the core database. However when testing this function the Dataset has completed the move of rows successfully internally, but upon using the TableAdapter.Update method the update of data to the database returns simply as "0" with no changes made or errors to lead.
Try
Dim activeRowMove As DataRow = Me.AssignmentDataSet.activeUnits.Rows(currentIndex)
Dim newMove As DataTable = Me.AssignmentDataSet.completedUnits
newMove.ImportRow(activeRowMove)
'UPDATE CHANGES
Me.Validate()
CompletedUnitsBindingSource.EndEdit()
Console.WriteLine(Me.CompletedUnitsTableAdapter.Update(Me.AssignmentDataSet.completedUnits))
activeUnitsTitle.Text = ("Assignment Completed!")
Catch ex As Exception
Console.WriteLine("Failed to update new table: " & ex.ToString)
activeUnitsTitle.Text = ("Failed to save!")
End Try
Is there somewhere in my code which I've simply done wrong, or any better or efficient way of moving a datarow is appreciated!
My database is not copied in my project's bin folder and every update goes to one central location.
If needed the following link is two screenshots of my current layout and data - here.
From MSDN:
Calling ImportRow preserves the existing DataRowState along with other values in the row
So, if the DataRowState in your "from" table is Unchanged, then its state will remained Unchanged in the "to" table, and it will not get saved.
Make sure to change the DataRowState of the newly imported row to Added
Cheers

MS Access - open a form taking a field value from a previous form

I have a form in an MS Access database which lists all the landowners consulted with for a new electricity line. At the end of each row is a button which opens another form, showing the details of all consultation, offers made etc.
I am trying to use vb in MS Access to take the contactID and automatically put it in a field in the details form, so that landowner's consultation details will pop up automatically. I am not a vb programmer at all (I have a comp sci degree mostly in Java and I'm currently working as a GIS analyst but it's a small company so I've been asked to get an Access database working).
I want to say
[detailsForm]![contactID] = [landownerlist]![ID]
in a way that vb and access will be happy with. Then I can see if I'm on the right track and if it will actually work! What I have above does not actually work. It won't compile.
From Kaliana
If you wish to open a form to a new record and to set the ID there, you can use Openargs, an argument of Openform:
DoCmd.OpenForm "FormName",,,,acFormAdd,,Me.ID
The opened form would also need some code:
If Me.Openargs<>vbNullstring Then
Me.Id = Me.Openargs
End If
It is also possible to find:
Forms!LandownersList.Recordset.FindFirst "ID=" & Me.ID
or fill in a value:
Forms!LandownersList!Id = Me.ID
on the form being opened from the calling form.
You may want to look into the code that is behind these buttons. If you are using a docmd.openform you can set the 4th Setting to a where clause on openning the next form.
DoCmd.OpenForm "OpenFormName", acNormal, , "[contactID] = " _
& [detailsForm]![contactID] , acFormEdit, acWindowNormal
This assumes contact ID is numeric and doesn't require any quotes.
Using open args is the generally accepted solution, as alluded to by others. This just falls under the category of "For you edification":) One of the problems with using open args is that unless you are careful with your comments it's easy to forget what they were supposed to mean. Were you passing more than one? Which is which? How did I do it here? How did I do it there etc. For my own money, I standardized to this (below) so I can always pass more than one argument without fear, and when I review my code a year from now, I can still see what's what without a huge hassle:
Option Explicit
'Example use: DoCmd.OpenForm "Example", OpenArgs:="Some Filter|True"
Public Enum eForm1Args
eFilter = 0
eIsSpecial = 1
End Enum
Private m_strArgs() As String
Public Property Get Args(ByVal eForm1Args As eForm1Args) As String
Args = m_strArgs(eForm1Args)
End Property
Private Sub Form_Open(Cancel As Integer)
m_strArgs = Split(Nz(Me.OpenArgs, vbNullString), "|")
If LenB(Me.Args(eFilter)) Then Me.Filter = Me.Args(eFilter)
End Sub
Private Sub Command1_Click()
If LCase$(Me.Args(eIsSpecial)) = "true" Then
'Do something special
End If
End Sub
As previously posted OpenArgs is great for this. One trick I have learned is that it is easy to pass in multiple parameters if required as a delimited string (comma for example), the target form can then access these values using the Split() function thus:
StringArrayVariable()= Split(me.OpenArgs,",")
Me.textbox= StringArrayVariable(0)
Me.textbox1= StringArrayVariable(1)
etc.
This is air code so check out the helpfile for Split().
It is also possible to pass objects in OpenArgs as well, it requires some manual memory pointer manipulation and I don't have the code to hand but I'm sure a Google search will find some examples. This technique can cause some random crashes though. Be Warned!

Resources