Toggle a DFrame with a button

I know this has been posted in about 20 different threads, I know this because I have read EVERY SINGLE ONE.
This thing is so annoying.

Okay, on players initial spawn, I give them these variables.


ply:SetNWBool( "gwarBuyMenuOpen", false )
  ply:SetNWBool( "gwarCanOpenMenu", true )

in cl_init.lua, I have this buy menu ( I would like to take this code down whenever possible because it’s for the gamemode I’ve been working on )


function drawBuyMenu()
  if LocalPlayer():GetNWBool( "gwarBuyMenuOpen" ) == false then
    LocalPlayer():SetNWBool( "gwarBuyMenuOpen", true )
  local cash = LocalPlayer():GetNetworkedInt( "money" )

  gwarform = vgui.Create( "DFrame" )
  gwarform:SetPos( ScrW()/2 - 300, ScrH()/2 - 150 )
  gwarform:SetSize( 600, 300 )
  gwarform:SetVisible( true )
  gwarform:SetTitle( "Buy Guns" )
  gwarform:SetDraggable( false )
  gwarform:ShowCloseButton( false )
  gwarform:MakePopup()

  local warmenuTabs = vgui.Create( "DPropertySheet", gwarform )
  warmenuTabs:SetPos( 5, 27 )
  warmenuTabs:SetSize( 590, 267 )

  local warmenu = vgui.Create( "DScrollPanel", gwarform )
  warmenu:SetPos( 0, 25 )
  warmenu:SetSize( 600, 275 )

  local moneyLabel = vgui.Create( "DLabel", warmenu )
  moneyLabel:SetText( "You have $" .. cash )
  moneyLabel:SetPos( 10, 20 )
  moneyLabel:SizeToContents()

  local ammoTip = vgui.Create( "DLabel", warmenu )
  ammoTip:SetText( "Be sure to buy a gun before ammo!" )
  ammoTip:SetPos( 290, 25 )
  ammoTip:SizeToContents()

  
  local closeButton = vgui.Create( "DButton" )
  closeButton:SetParent( gwarform )
  closeButton:SetText( "Close" )
  closeButton:SetPos( 480, 10 )
  closeButton:SetSize( 100, 30 )
  closeButton.DoClick = function()
    self:Close()
    gui.EnableScreenClicker( false )
    LocalPlayer():SetNWBool( "gwarBuyMenuOpen", false )
  end


  for k,v in pairs( gmodWarfareBuyWeapons ) do
    local DermaButton = vgui.Create( "DButton" )
    DermaButton:SetParent( warmenu )
    DermaButton:SetText( "Buy " .. gmodWarfareBuyWeapons[k].name .. " ($" .. gmodWarfareBuyWeapons[k].price .. " )" )
    DermaButton:SetPos( 25, k * 40 )
    DermaButton:SetSize( 250, 30 )
    DermaButton.DoClick = function()
      RunConsoleCommand("gwar_buyWeapon", gmodWarfareBuyWeapons[k].weapon )
    end
  end

  for k,v in pairs( gmodWarfareBuyAmmo ) do
    local DermaButton = vgui.Create( "DButton" )
    DermaButton:SetParent( warmenu )
    DermaButton:SetText( "Buy " .. gmodWarfareBuyAmmo[k].name .. " ($" .. gmodWarfareBuyAmmo[k].price .. ")" )
    DermaButton:SetPos( 290, k * 40 )
    DermaButton:SetSize( 135, 30 )
    DermaButton.DoClick = function()
      RunConsoleCommand("gwar_buyAmmo", gmodWarfareBuyAmmo[k].ammo )
    end
  end

  for k,v in pairs( gmodWarfareBuyAmmo ) do
    local DermaButton = vgui.Create( "DButton" )
    DermaButton:SetParent( warmenu )
    DermaButton:SetText( "Buy " .. gmodWarfareBuyAmmo[k].name .. "x10" )
    DermaButton:SetPos( 435, k * 40 )
    DermaButton:SetSize( 120, 30 )
    DermaButton.DoClick = function()
      for i = 1,10 do
        RunConsoleCommand("gwar_buyAmmo", gmodWarfareBuyAmmo[k].ammo )
      end
    end
  end

  local warmenuGear = vgui.Create( "DScrollPanel", gwarform )
  warmenuGear:SetPos( 0, 25 )
  warmenuGear:SetSize( 600, 275 )

  for k,v in pairs( gmodWarfareGear ) do
    local DermaButton = vgui.Create( "DButton" )
    DermaButton:SetParent( warmenuGear )
    DermaButton:SetText( "Buy " .. gmodWarfareGear[k].name .. " ($" .. gmodWarfareGear[k].price .. ")" )
    DermaButton:SetPos( 25, k * 40 )
    DermaButton:SetSize( 250, 30 )
    DermaButton.DoClick = function()
      RunConsoleCommand("gwar_buyGear", gmodWarfareGear[k].name )
    end
  end

  warmenuTabs:AddSheet( "Guns/Ammo", warmenu, "gui/silkicons/shield", false, false, nil )
  warmenuTabs:AddSheet( "Gear", warmenuGear, "gui/silkicons/shield", false, false, nil )

  if LocalPlayer():IsAdmin() then
    local warmenuAdminTab = vgui.Create( "DScrollPanel", gwarform )
    warmenuAdminTab:SetPos( 0, 25 )
    warmenuAdminTab:SetSize( 600, 275 )

    local adminText = vgui.Create( "DLabel", warmenuAdminTab )
    adminText:SetText( "Welcome to the admin menu" )
    adminText:SetPos( 10, 25 )
    adminText:SizeToContents()

    warmenuTabs:AddSheet( "Admin", warmenuAdminTab, "gui/silkicons/shield", false, false, nil )
  end

  else
    gwarform:Close()
    gui.EnableScreenClicker( false )
    LocalPlayer():SetNWBool( "gwarBuyMenuOpen", false )
  end
end

To check if players are hitting the Q button, I have


hook.Add( "Think", "OpenBuyMenu", function()
  if input.IsKeyDown( KEY_Q ) then
    if LocalPlayer():GetNWBool( "gwarCanOpenMenu" ) == true then
      LocalPlayer():SetNWBool( "gwarCanOpenMenu", false )
      drawBuyMenu()
      timer.Simple( 1, function() LocalPlayer():SetNWBool( "gwarCanOpenMenu", true ) end )
    end
  end
end )


Now, Notice I DO HAVE a timer to prevent the player from openining the buy menu too quickly.

I have the hand made close button


local closeButton = vgui.Create( "DButton" )
  closeButton:SetParent( gwarform )
  closeButton:SetText( "Close" )
  closeButton:SetPos( 480, 10 )
  closeButton:SetSize( 100, 30 )
  closeButton.DoClick = function()
    self:Close()
    gui.EnableScreenClicker( false )
    LocalPlayer():SetNWBool( "gwarBuyMenuOpen", false )
  end

So that the player will get pushed the "gwarBuyMenuOpen variable.

This works, but sometimes, the buy menu will open twice. When I close the first one, it closes, when I close the second one, I get.


[ERROR] gamemodes/gmodwarfare/gamemode/cl_init.lua:45: attempt to index global 'self' (a nil value)
  1. DoClick - gamemodes/gmodwarfare/gamemode/cl_init.lua:45
   2. unknown - lua/vgui/dlabel.lua:206


lines 39 - 48 are


local closeButton = vgui.Create( "DButton" )
  closeButton:SetParent( gwarform )
  closeButton:SetText( "Close" )
  closeButton:SetPos( 480, 10 )
  closeButton:SetSize( 100, 30 )
  closeButton.DoClick = function()
    self:Close()
    gui.EnableScreenClicker( false )
    LocalPlayer():SetNWBool( "gwarBuyMenuOpen", false )
  end

The menu will be on my screen in plane site, but it doesn’t exist.
The way I replicate this is just to open and close the menu a few times.

There are no other functions changing these variables on the player, nor is there anything auto opening the menu.

I have tried registering panels when they come in, then removing them all, but that always errors out.

Any questions PLEASE ask, but what is going wrong?

Change self to gwarform

That’s what I had it set to originally, it gives me


[ERROR] [ERROR] gamemodes/gmodwarfare/gamemode/cl_init.lua:45: attempt to call method 'Close' (a nil value)
  1. DoClick - gamemodes/gmodwarfare/gamemode/cl_init.lua:45
   2. unknown - lua/vgui/dlabel.lua:206

Actually, you’ll still have issues with it being opened multiple times since gwarform isn’t local. Do you need it be global?

Two changes will leave you error free, depending on the rest of your code:
[lua]gwarform = vgui.Create( “DFrame” )
–should be changed it to make ‘local’
local gwarform = vgui.Create( “DFrame” )

closeButton.DoClick = function()
self:Close()
–should be
closeButton.DoClick = function()
gwarform:Close()
[/lua]

The reason I made it global was for the close feature.

Line 117 - 121


else
    gwarform:Close()
    gui.EnableScreenClicker( false )
    LocalPlayer():SetNWBool( "gwarBuyMenuOpen", false )
  end

If gwarform is local I get


[ERROR] gamemodes/gmodwarfare/gamemode/cl_init.lua:118: attempt to index global 'gwarform' (a nil value)
  1. drawBuyMenu - gamemodes/gmodwarfare/gamemode/cl_init.lua:118
   2. v - gamemodes/gmodwarfare/gamemode/cl_init.lua:174
    3. unknown - lua/includes/modules/hook.lua:84


It was hard to tell with the indenting you had. You don’t need it global, you simply need to change where abouts it is being declared local. If you declare it inside the if statement, then the ‘else’ section has no idea that variable exists (variable scoping). Declare it above it, and you are set. This is it tidied up (untested).

[lua]function drawBuyMenu()
local gwarform --now you won’t get the error when closing
if LocalPlayer():GetNWBool( “gwarBuyMenuOpen” ) == false then
LocalPlayer():SetNWBool( “gwarBuyMenuOpen”, true )
local cash = LocalPlayer():GetNetworkedInt( “money” )

	gwarform = vgui.Create( "DFrame" )
	gwarform:SetPos( ScrW()/2 - 300, ScrH()/2 - 150 )
	gwarform:SetSize( 600, 300 )
	gwarform:SetVisible( true )
	gwarform:SetTitle( "Buy Guns" )
	gwarform:SetDraggable( false )
	gwarform:ShowCloseButton( false )
	gwarform:MakePopup()

	local warmenuTabs = vgui.Create( "DPropertySheet", gwarform )
	warmenuTabs:SetPos( 5, 27 )
	warmenuTabs:SetSize( 590, 267 )

	local warmenu = vgui.Create( "DScrollPanel", gwarform )
	warmenu:SetPos( 0, 25 )
	warmenu:SetSize( 600, 275 )

	local moneyLabel = vgui.Create( "DLabel", warmenu )
	moneyLabel:SetText( "You have $" .. cash )
	moneyLabel:SetPos( 10, 20 )
	moneyLabel:SizeToContents()

	local ammoTip = vgui.Create( "DLabel", warmenu )
	ammoTip:SetText( "Be sure to buy a gun before ammo!" )
	ammoTip:SetPos( 290, 25 )
	ammoTip:SizeToContents()


	local closeButton = vgui.Create( "DButton" )
	closeButton:SetParent( gwarform )
	closeButton:SetText( "Close" )
	closeButton:SetPos( 480, 10 )
	closeButton:SetSize( 100, 30 )
	closeButton.DoClick = function()
		gwarform:Close()
		gui.EnableScreenClicker( false )
		LocalPlayer():SetNWBool( "gwarBuyMenuOpen", false )
	end


	for k,v in pairs( gmodWarfareBuyWeapons ) do
		local DermaButton = vgui.Create( "DButton" )
		DermaButton:SetParent( warmenu )
		DermaButton:SetText( "Buy " .. gmodWarfareBuyWeapons[k].name .. " ($" .. gmodWarfareBuyWeapons[k].price .. " )" )
		DermaButton:SetPos( 25, k * 40 )
		DermaButton:SetSize( 250, 30 )
		DermaButton.DoClick = function()
			RunConsoleCommand("gwar_buyWeapon", gmodWarfareBuyWeapons[k].weapon )
		end
	end

	for k,v in pairs( gmodWarfareBuyAmmo ) do
		local DermaButton = vgui.Create( "DButton" )
		DermaButton:SetParent( warmenu )
		DermaButton:SetText( "Buy " .. gmodWarfareBuyAmmo[k].name .. " ($" .. gmodWarfareBuyAmmo[k].price .. ")" )
		DermaButton:SetPos( 290, k * 40 )
		DermaButton:SetSize( 135, 30 )
		DermaButton.DoClick = function()
			RunConsoleCommand("gwar_buyAmmo", gmodWarfareBuyAmmo[k].ammo )
		end
	end

	for k,v in pairs( gmodWarfareBuyAmmo ) do
		local DermaButton = vgui.Create( "DButton" )
		DermaButton:SetParent( warmenu )
		DermaButton:SetText( "Buy " .. gmodWarfareBuyAmmo[k].name .. "x10" )
		DermaButton:SetPos( 435, k * 40 )
		DermaButton:SetSize( 120, 30 )
		DermaButton.DoClick = function()
			for i = 1,10 do
				RunConsoleCommand("gwar_buyAmmo", gmodWarfareBuyAmmo[k].ammo )
			end
		end
	end

	local warmenuGear = vgui.Create( "DScrollPanel", gwarform )
	warmenuGear:SetPos( 0, 25 )
	warmenuGear:SetSize( 600, 275 )

	for k,v in pairs( gmodWarfareGear ) do
		local DermaButton = vgui.Create( "DButton" )
		DermaButton:SetParent( warmenuGear )
		DermaButton:SetText( "Buy " .. gmodWarfareGear[k].name .. " ($" .. gmodWarfareGear[k].price .. ")" )
		DermaButton:SetPos( 25, k * 40 )
		DermaButton:SetSize( 250, 30 )
		DermaButton.DoClick = function()
			RunConsoleCommand("gwar_buyGear", gmodWarfareGear[k].name )
		end
	end

	warmenuTabs:AddSheet( "Guns/Ammo", warmenu, "gui/silkicons/shield", false, false, nil )
	warmenuTabs:AddSheet( "Gear", warmenuGear, "gui/silkicons/shield", false, false, nil )

	if LocalPlayer():IsAdmin() then
		local warmenuAdminTab = vgui.Create( "DScrollPanel", gwarform )
		warmenuAdminTab:SetPos( 0, 25 )
		warmenuAdminTab:SetSize( 600, 275 )

		local adminText = vgui.Create( "DLabel", warmenuAdminTab )
		adminText:SetText( "Welcome to the admin menu" )
		adminText:SetPos( 10, 25 )
		adminText:SizeToContents()

		warmenuTabs:AddSheet( "Admin", warmenuAdminTab, "gui/silkicons/shield", false, false, nil )
	end

else
	gwarform:Close()
	gui.EnableScreenClicker( false )
	LocalPlayer():SetNWBool( "gwarBuyMenuOpen", false )
end

end[/lua]

[editline]30th July 2014[/editline]

You would still get an error when closing if the form doesn’t exist. Maybe change gwarform:Close() to [lua]if IsValid(gwarform) then gwarform:Close() end[/lua] just in case.

EDIT: With this exact code, I can open the menu with the q button and I can close it with the in-menu close button, but I can’t close the menu with the q button. After the else statement, gwarform is still nil :frowning:

Correct me if I’m wrong here, but I don’t see anywhere where you set it to close if Q is pressed again? Do you want it to be a toggle or a hold and release thing?

In drawBuyMenu() I check


if LocalPlayer():GetNWBool( "gwarBuyMenuOpen" ) == false then
LocalPlayer():SetNWBool( "gwarBuyMenuOpen", true )
--Draw buy menu

else
if IsValid(gwarform) then gwarform:Close() end
    gui.EnableScreenClicker( false )
    LocalPlayer():SetNWBool( "gwarBuyMenuOpen", false )


The problem is, in closing it, gwarform is nil

Covers opening, but not toggling ( F1-F4 )
https://dl.dropboxusercontent.com/u/26074909/tutoring/vgui/open_vgui_based_on_keypress.lua.html

Covers using TAB tap to open and remain open, tap to close OR holding tab to keep open and releasing to close: https://dl.dropboxusercontent.com/u/26074909/tutoring/vgui/open_menu_key_tap_open_and_key_hold_release_close.lua.html

Covers using a key to close a menu. ( Add PlayerBindPress or a different key-detecting hook or Think to detect the key being pressed to open it )
https://dl.dropboxusercontent.com/u/26074909/tutoring/vgui/open_menu_key_tap_using_bind.lua

Hopefully these help. Oh, set the local gwar… to the file, not the function of OpenMenu if you want a different portion of the script to capture it. Otherwise set up a PANEL like one of the examples above.

After tinkering with option number 3 a bit, I got it to work. If I have any more problems then I will bump this thread, but for now I will mark it as solved and put you in a note for credit, thanks!