Making draggable frames

From Warcraft Wiki
Jump to navigation Jump to search

Frames are made draggable by calling Frame:SetMovable and Frame:StartMoving.

Example

OnDragStart

Requires moving the mouse a bit before it starts dragging.

local f = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
f:SetPoint("CENTER")
f:SetSize(200, 200)
f:SetBackdrop(BACKDROP_TUTORIAL_16_16)

f:SetMovable(true)
f:EnableMouse(true)
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function(self, button)
	self:StartMoving()
	print("OnDragStart", button)
end)
f:SetScript("OnDragStop", function(self)
	self:StopMovingOrSizing()
	print("OnDragStop")
end)

OnMouseDown

This is more responsive as it immediately starts dragging.

local f = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
f:SetPoint("CENTER")
f:SetSize(200, 200)
f:SetBackdrop(BACKDROP_TUTORIAL_16_16)

f:SetMovable(true)
f:SetScript("OnMouseDown", function(self, button)
	self:StartMoving()
	print("OnMouseDown", button)
end)
f:SetScript("OnMouseUp", function(self, button)
	self:StopMovingOrSizing()
	print("OnMouseUp", button)
end)

Outdated section

Draggable frames can be moved by having the user hold down a mouse button over the frame, then move the mouse to reposition the frame. This HOWTO describes how to make a frame draggable.

Summary: Flag a frame as movable (either through the movable XML attribute or frame:SetMovable(isMovable)). To initiate dragging of a frame, call frame:StartMoving(); to stop, call frame:StopMovingOrSizing(). These functions may be called from OnMouseDown/OnMouseUp or OnDragStart/OnDragStop handlers. Use frame:EnableMouse(true) or frame:RegisterForDrag("LeftButton") to activate these widget handlers.

If you want WoW to save the position of your frame between sessions without writing additional code, you'll need to create the frame, using a non-nil name, and flag it as movable before PLAYER_LOGIN fires.

Using XML

The code below creates a draggable Frame widget and uses the OnDrag widget handlers to initiate dragging. Note the use of movable and enableMouse attributes, as well as the OnLoad script allowing the frame to be dragged around using the left mouse button. The self.isLocked check in the OnDragStart handler illustrates that you can easily implement frame locking by not having your widget handlers call :StartMoving() when the frame is locked.

<Frame name="DragFrame1" enableMouse="true" movable="true">
 <Scripts>
  <OnLoad>
   self:RegisterForDrag("LeftButton")
  </OnLoad>
  <OnDragStart>
   if not self.isLocked then
    self:StartMoving()
   end
  </OnDragStart>
  <OnDragStop>
   self:StopMovingOrSizing()
  </OnDragStop>
 </Scripts>
 <!-- Tags below add a visual element to the frame. -->
 <Layers>
  <Layer level="ARTWORK">
   <Texture setAllPoints="true">
    <Color r="1.0" g="0.5" b="0.0" a="0.5" />
   </Texture>
  </Layer>
 </Layers>
 <Size x="64" y="64" />
 <Anchors><Anchor point="CENTER" relativeTo="UIParent"/></Anchors>
</Frame>

While OnDrag* handlers typically require the mouse button to be held down for a small amount of time prior to enabling the mouse behavior, making them well suited for dragging widgets that normally respond to clicks. However, if the frame you wish to make draggable is not normally a button, you can use OnMouseUp/OnMouseDown to provide a more responsive experience. If OnMouseDown/OnMouseUp were used instead of OnDragStart/OnDragStop in the example above, the OnLoad handler could be omitted.

Using TitleRegion

Frames can have a TitleRegion object that handles dragging automatically -- as long as the mouse is held down within the TitleRegion, it'll allow the frame to be dragged.

The frame created below will be draggable by clicking on its top 20 pixels:

<Frame enableMouse="true">
 <Size x="100" y="100"/>
 <TitleRegion>
  <Size x="100" y="20"/>
  <Anchors><Anchor point="TOP"/></Anchors>
 </TitleRegion>
 <!-- Tags below add a visual element to the frame. -->
 <Layers>
  <Layer level="ARTWORK">
   <Texture setAllPoints="true">
    <Color r="1.0" g="0.5" b="0.0" a="0.5" />
   </Texture>
  </Layer>
 </Layers>
 <Anchors><Anchor point="CENTER" relativeTo="UIParent"/></Anchors>
</Frame>

Using Lua

The OnDragStart/OnDragStop and OnMouseDown/OnMouseUp methods translate trivially to an entirely-in-Lua implementation, shown below.

local frame = CreateFrame("Frame", "DragFrame2", UIParent)
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function(self)
    self:StartMoving()
  end)
frame:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
  end)

-- The code below makes the frame visible, and is not necessary to enable dragging.
frame:SetPoint("CENTER")
frame:SetSize(64, 64)
local tex = frame:CreateTexture("ARTWORK")
tex:SetAllPoints()
tex:SetColorTexture(1.0, 0.5, 0, 0.5)

Note that the StartMoving and StopMovingOrSizing widget methods can be reused as widget handlers in this case, avoiding the creation of additional functions.

Notes

  • If you do not wish to allow the user to (accidentally) drag your frame off-screen, you can add the clampedToScreen="true" to its XML declaration, or use frame:SetClampedToScreen(true)
  • If the frame you intend to move is not the frame you are setting the script on (e.g. self:GetParent():StartMoving()), it is best to use OnMouseDown/Up because OnDragStop will not fire when the mouse button is released which will leave the frame stuck on the cursor. This may be due to the fact that when you start moving a different frame (i.e. the frame's parent frame), the dragging of the source frame stops because either the mouse's focus is shifted to the other frame or the client can only keep track of one moving frame at a time. Either way, this happens before the client even knows it was dragging a frame.
  • You cannot use OnMouseDown to move a frame and OnDragStart/Stop at the same time on the same frame. This is because when you click the frame :StartMoving() is immediately called and you can't drag a frame that's already moving.