How can I make a dropdown box of materials and run code when one is selected?
Use pointers.
EDIT, having a property to keep the name, and use in a property search has IMO been superseded by assigning a pointer property with a poll in later (circa late 2.79 and beyond).
Example, select from and assign a property to a material of all non grease pencil materials in blend :
Custom search data to UILayout.prop_search

Finding only the materials in the scene would be doable (make a set of all materials of all objects in scene)  but is probably not necessary, instead suggest using bpy.data.materials the list of all materials in the file.
EDIT: However have done this here
How to populate UIList with all material slot in scene? 2.8
A UILayout.prop_search can be used as a UI to assign a material's name to a string property.
import bpy
from bpy.props import StringProperty

class LayoutDemoPanel(bpy.types.Panel):
    bl_label = &quot;Set Active Material&quot;
    bl_idname = &quot;OBJECT_PT_example&quot;
    bl_space_type = 'VIEW_3D'
    bl_region_type = &quot;TOOLS&quot;
    bl_category = 'Materials'

    @classmethod
    def poll(cls, context):
        return len(bpy.data.materials) &gt; 0

    def draw(self, context):
        layout = self.layout

        scene = context.scene

        layout.prop_search(scene,
                           &quot;selected_mat&quot;,
                           bpy.data,
                           &quot;materials&quot;)

def register():
    bpy.types.Scene.selected_mat = StringProperty(default=&quot;None&quot;)    
    bpy.utils.register_class(LayoutDemoPanel)


def unregister():
    bpy.utils.unregister_class(LayoutDemoPanel)


if __name__ == &quot;__main__&quot;:
    register()


In an operator can use the property like so.
scene = context.scene
material = bpy.data.materials.get(scene.selected_mat)
if material is None:
    # no material has it been renamed?
    return {'CANCELLED'}
# do something with material