function CW_itMultiDataLevel, Parent, oUI, $
    COLORS=colors, $
    COLUMN=column, $
    DATA_LABEL=dataLabel, $
    DATA_USE_INDEX=useDataIndex, $
    DATA_OBJECTS=dataObjects, $
    DATA_RANGE=dataRange, $
    EXTENDABLE_RANGES=extendRanges, $
    INITIAL_VALUES=initialValues, $
    LEVEL_NAMES=levelNames, $
    NLEVELS=nLevels, $
    PALETTE_OBJECTS=paletteObjects, $
    UVALUE=uvalue, $
    VERTICAL=vertical, $
    XSIZE=xSize, $
    YSIZE=ySize

    ;; Pragmas
    compile_opt idl2, hidden

nparams = 2  ; must be defined for cw_iterror
@cw_iterror

    if N_ELEMENTS(xSize) eq 0 then $
        xSize = 256
    if N_ELEMENTS(ySize) eq 0 then $
        ySize = 128

    ;; Make sure that there is at least one level
    nLevels = N_ELEMENTS(nLevels) eq 0 ? 1 : nLevels

    nDataSets = N_ELEMENTS(dataObjects)

    ;; Check to be sure data objects are valid.
    for i=0, nDataSets-1 do $
        if ~OBJ_VALID(dataObjects[i]) then return, 0

    bHaveInitValues = 0b
    if (N_ELEMENTS(initialValues) gt 0) then begin
        if N_ELEMENTS(initialValues) ne nLevels*nDataSets then begin
            oTool = oUI->GetTool()
            if (OBJ_VALID(oTool)) then $
              oTool->SignalError, $
              IDLitLangCatQuery('UI:cwMultDataLevel:BadInitVal')
        endif else $
            bHaveInitValues = 1b
    endif

    ;; main base
    wBase = WIDGET_BASE(Parent, /COLUMN, /FRAME, SPACE=0, $
        EVENT_FUNC='CW_itMultiDataLevel_Event', UVALUE=uvalue, $
        PRO_SET_VALUE='CW_itMultiDataLevel_SetValue', $
        FUNC_GET_VALUE='CW_itMultiDataLevel_GetValue')


    ;; Prepare selection drop list
    wRow = WIDGET_BASE(wBase, /ROW, YPAD=0)
    if (N_ELEMENTS(dataLabel) ne 0) then $
        wLabel = WIDGET_LABEL(wRow, VALUE=dataLabel)
    if (KEYWORD_SET(useDataIndex)) then $
        dropValue = ['000'] $
    else $
        dynamicResize = 1
    wDropList = WIDGET_DROPLIST(wRow, VALUE=dropValue, $
        DYNAMIC_RESIZE=dynamicResize)

    if nDataSets gt 0 then begin
        dataNames = STRARR(nDataSets)
        if (KEYWORD_SET(useDataIndex)) then begin
            for i=0, nDataSets-1 do $
                dataNames[i] = STRTRIM(STRING(i),2)
        endif else begin
            for i=0, nDataSets-1 do begin
                dataObjects[i]->GetProperty, NAME=dataName
                dataNames[i] = dataName
            endfor
        endelse
        WIDGET_CONTROL, wDropList, SET_VALUE=dataNames
    endif
    WIDGET_CONTROL, wDropList, SENSITIVE=nDataSets gt 1

    ;; Apply all button
    wApplyBase = WIDGET_BASE(wBase, /NONEXCLUSIVE, YPAD=0, SPACE=0)
    wApplyAll = WIDGET_BUTTON(wApplyBase, $
                              VALUE=IDLitLangCatQuery('UI:cwMultDataLevel:ApplyAll'), $
                              SENSITIVE = nDataSets gt 1)

    ;; Now the data level widget.
    wDataLevel = CW_ITDATALEVEL(wBase, oUI, $
                                COLORS=colors, $
                                COLUMN=column, $
                                DATA_RANGE=dataRange, $
                                EXTENDABLE_RANGES=KEYWORD_SET(extendRanges), $
                                INITIAL_VALUES=(bHaveInitValues ? $
                                                initialValues[*,0] : $
                                                notDefined), $
                                LEVEL_NAMES=levelNames, $
                                NLEVELS=nLevels, $
                                VERTICAL=vertical, $
                                XSIZE=xsize, $
                                YSIZE=ysize, YPAD=0)


    ;; Build our state structure
    state = { $
              oUI: oUI, $
              pDataObjects: PTR_NEW(), $
              pDataNames: PTR_NEW(), $
              pLevelNames: PTR_NEW(), $
              pLevelData: PTR_NEW(), $
              pPaletteObjects: PTR_NEW(), $
              currentDataset: 0, $
              wDropList: wDropList, $
              wDataLevel: wDataLevel, $
              wApplyAll: wApplyAll, $
              useDataIndex: KEYWORD_SET(useDataIndex), $
              minMax: [0.0d, 0] $
            }

    ;; Create dynamic state data
    if (N_ELEMENTS(levelNames) ne 0) then $
        state.pLevelNames = PTR_NEW(levelNames) $
    else $
        state.pLevelNames = PTR_NEW(/ALLOC)

    if nDataSets gt 0 then begin
        state.pDataObjects = PTR_NEW(dataObjects)
        state.pDataNames = PTR_NEW(dataNames)
        state.pLevelData = PTR_NEW(DBLARR(nLevels, nDataSets))
        if N_ELEMENTS(paletteObjects) eq nDataSets then begin
            state.pPaletteObjects = PTR_NEW(paletteObjects)
        endif else begin
            state.pPaletteObjects = PTR_NEW(OBJARR(nDataSets))
        endelse
    endif else begin
        state.pDataObjects = PTR_NEW(/ALLOC)
        state.pDataNames = PTR_NEW(/ALLOC)
        state.pLevelData = PTR_NEW(/ALLOC)
        state.pPaletteObjects = PTR_NEW(/ALLOC)
    endelse

    ;; Fill in our array of level data
    if bHaveInitValues then begin
        *state.pLevelData = initialValues
    endif else begin
        for i=0, nDataSets-1 do begin
            ;; This trick lets the DataLevel widget do the work
            ;; of setting initial values for us.
            value = {DATA_OBJECT:dataObjects[i]}
            WIDGET_CONTROL, wDataLevel, SET_VALUE=value
            WIDGET_CONTROL, wDataLevel, GET_VALUE=value
            (*state.pLevelData)[*, i] = value.level_Values
        endfor
    endelse

    ;; Now set the values for dataset 0 in the DataLevel widget
    if nDataSets gt 0 then begin
        if N_ELEMENTS(paletteObjects) ne nDataSets then begin
            value = {DATA_OBJECT:dataObjects[0], $
                     LEVEL_VALUES: DOUBLE((*state.pLevelData)[*, 0])}
        endif else begin
            value = {DATA_OBJECT:dataObjects[0], $
                     LEVEL_VALUES: DOUBLE((*state.pLevelData)[*, 0]), $
                     PALETTE_OBJECT:paletteObjects[0]}
        endelse
        WIDGET_CONTROL, wDataLevel, SET_VALUE=value
    endif

    wChild = WIDGET_INFO(wBase, /CHILD)
    WIDGET_CONTROL, wChild, SET_UVALUE=state, /NO_COPY
    WIDGET_CONTROL, wChild, KILL_NOTIFY='CW_itMultiDataLevel_KillNotify'

    return, wBase
end