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.
Related
Is there a way to remove a number from an attibute array in an update? For example, if I want to update all of an alchy's booze stashes if he runs out of a particular type of booze:
Alchy has_many :stashes
Stash.available_booze_types = [] (filled with booze.ids)
Booze is also a class
#booze.id = 7
if #booze.is_all_gone
#alchy.stashes.update(available_booze_types: "remove #booze.id")
end
update: #booze.id may or may not be present in the available_booze_types array
... so if #booze.id was in any of the Alchy.stash instances (in the available_booze_types attribute array), it would be removed.
I think you can do what you want in the following way:
if #booze.is_all_gone
#alchy.stashes.each do |stash|
stash.available_booze_types.delete(#booze.id)
end
end
However, it looks to me like there are better ways to do what you are trying to do. Rails gives you something like that array by using relations. Also, the data in the array will be lost if you reset the app (if as I understand available_booze_types is an attribute which is not stored in a database). If your application is correctly set up (an stash has many boozes), an scope like the following in Stash class seems to me like the correct approach:
scope :available_boozes, -> { joins(:boozes).where("number > ?", 0) }
You can use it in the following way:
#alchy.stashes.available_boozes
which would only return the ones that are available.
I am trying to code a part of a software where I try to show the results that match search criteria.
I have a textbox where I can type one or more words I want to search and a listview that contains 4 different columns and a dozen rows. The idea is that each listview row contains lots of words and I want to see only the rows that contain all the words I have typed in the textbox. I have finished the code that searches for one term only. The problem I am having is that I don't fully understand how to do the same, but using multiple terms instead of one term only.
In the textbox, I write the words I want to search separated by a space. I have a variable where I keep the whole content of the listview row separated by : (example => col1row1content:col1row2content:col1row3content,etc). Summarizing, I want to check if a string (the full content of a row) contains all other strings (each word I have typped in the textbox).
This is the code I have implemented:
Dim textboxFullContentArray As String() = textboxSearch.Split(New Char() {" "c})
Dim Content As String
Dim containsAll As Boolean = False
Dim wholeRowContent(listviewMain.Items.Count - 1) As String ' each index of the array keeps the entire row content (one array contains all 4 cells of the row)
' wholeRowContent contains in one index the entire content of a row. That means,
' the index contains the 4 cells that represent an entire row.
' The format is like "rowData1:rowData2:rowData3:rowData4" (omitted for simplicity)
For Q As Integer = 0 To listviewMain.Items.Count - 1
For Each Content In textboxFullContentArray
If wholeRowContent(Q).ToLower.Contains(Content) Then
containsAll = True
' rest of the code...
ElseIf Not wholeRowContent(Q).ToLower.Contains(Content) Then
containsAll = False
Exit For
End If
Next
Next
But of course, this code is showing false positives and I think it's not a good solution. I think it must be much easier and I am overcomplicating the concept.
I am using VB.Net 2013
You can determine whether a String contains all of a list of substrings with a single line of code:
If substrings.All(Function(s) str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0) Then
Notice that I have actually implemented a case-insensitive comparison, rather than using ToLower or ToUpper.
It may not seem as neat to call IndexOf rather than Contains but guess what: Contains actually calls IndexOf internally anyway:
public bool Contains(string value)
{
return this.IndexOf(value, StringComparison.Ordinal) >= 0;
}
You can write your own extension methods if you want a case-insensitive Contains method:
<Extension>
Public Function Contains(source As String,
value As String,
comparisonType As StringComparison) As Boolean
Return source.IndexOf(value, comparisonType) >= 0
End Function
Your If/Else looks like it could be simplified. I would set your containsAll value to true outside the nested loops, and only if you encounter a "Content" in "textboxFullContentArray" that is not contained in wholeRowContent(Q) you set containsAll to false, otherwise do nothing.
Also, one way to see what's going on is to print statements with the values that are being compared throughout your function, which you can read through and see what is happening at runtime when the false positives occur.
After some hours looking for a simple and effective solution (and trying different codes), I have finally found this solution that I adapted from: Bad word filter - stackoverflow
For Q As Integer = 0 To listviewMain.Items.Count - 1
If textboxFullContentArray.All(Function(b) wholeRowContent(q).ToLower().Contains(b.ToLower())) Then
' my code
End If
Next
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.
I've created a game which loops through a table of properties to create enemies to place on screen. The created enemies are stored in a variable called "baddie", and things like their x and y value are determined by the properties I gave them in the table. Currently, "baddie" creates 3 enemies at varying spots on screen. It looks something like this.
for i=1, #level[section]["enemies"] do
local object = level[section]["enemies"][i]
baddie = display.newSprite(baddieSheet, baddieData)
baddie.anchorX = 0.5
baddie.anchorY = 1
baddie.x = object["position"][1]; baddie.y = object["position"][2];
baddie.xScale = -1
baddie.myName = "Baddie"
baddie.health = 15
baddie:setSequence("standrt"); baddie:play()
physics.addBody(baddie, "dynamic", {radius=22, density=0.1, friction=10.0, bounce=0.0})
baddie.isFixedRotation = true
enemyGroup:insert(baddie)
end
I then inserted all of the created instances stored in the baddie variable, into a display group called "enemyGroup."
Now here's my question. I'm working on my game's AI and storing it all in an enterFrame listener. I want to make a "True/False" flag called "inRange." When the enemy's x position is within 20 pixels of the player's x, inRange = true. When it's true, the enemy will attack him. But I haven't figured out a way to make the inRange flag check for each individual enemy, instead of all of them.
I was thinking of something like,
for i = 1, enemyGroup.numChildren do
enemyGroup[i].widthBetween = enemyGroup[i].x - sprite.x
if enemyGroup[i].widthBetween <= 20 and enemyGroup[i].widthBetween >= -20 then
enemyGroup[i].inRange = true
else
enemyGroup[i].inRange = false
end
end
But the issue is, enemyGroup[i].inRange is a local value and I can't call for it in outside of the loop or in other functions. This is obviously problematic, because in another function I want to have each individual enemy punch, roll, jump, etc when their individual inRange property is true. Is there a way I can store enemyGroup[i].inRange so that I can call for it whenever?
Sorry if this question is confusing. It's been a struggle to word it.
I'm not sure why this isn't working for you. enemyGroup[i].inRange is not local, its an attribute of the object at enemyGroup[i]. It should be avalble anywhere you can access enemyGroup[i].
Personally I would have not used a display.newGroup() for this, instead I would have just created an array/table that's scoped for the whole scene.
local baddies = {}
then in your loop:
--enemyGroup:insert(baddie) instead of this, do this:
baddies[#baddies + 1] = baddie
Then you have a table that you can loop over, but it's really more code style than functionality. As long as your enemyGroup is scoped at a high enough level that any function that the scene can see.
you should create a file in the structure below:
module(..., package.seeall)
enemyGroup = {}
and in all your files where you want to use this table, first of all require this file( assume you named this file enemies.lua):
local enemiesArray = require "enemies"
-- somewhere in your code:
enemiesArray.enemyGroup[i].isRange = true -- or whatever you like to do
there is one better option for you to use _G variable. when you store an object to _G you can access that wherever you want( like the famous design pattern Singleton ). just set the variable one time and use it anywhere and as much as you want. for example:
-- in one file you set enemy table:
_G.enemies = enemyGroup
-- somewhere else in nowhere :)
print(_G.enemies.isRange)
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!