MultilineInputBox (listbox & multilineedit examples) [MM2+3]

This forum is for questions / discussions regarding development of addons / tweaks for MediaMonkey.

Moderator: Gurus

ZvezdanD
Posts: 3090
Joined: Thu Jun 08, 2006 7:40 pm

MultilineInputBox (listbox & multilineedit examples) [MM2+3]

Post by ZvezdanD » Sun Dec 14, 2008 5:23 pm

As some would like to say - this post is not for users but for scripters. There is not some new script, but explanation and sample code how to get listboxes and multi-line edit controls within your scripts. As you could see with the new (2.0) version of the Magic Nodes script, they are possible to get even with MM2 which don't have built-in multi-line edit control. However, my multi-line edit control is even better then MM3 built-in multi-line edit control! (I'll tell you why somewhat latter). I am sorry if there already exists some script which has something similar like my suggestion, but in that case its author also should be sorry why s/he didn't share his experience with us when we asked for such thing (there are several requests for listbox control in the Scripting Functionality Wishlist thread without any answer).

Some of us have been using some kind of workaround with Forms ActiveX which contain Listbox and Textbox (which have Multiline property), but this solution is not very well for several reasons. First of all, Forms ActiveX should be downloaded from Microsoft site if you don't have installed MS Office, then you should register it. Those controls from this ActiveX have plain old look from Win 95 days, even if you have XP or Vista. This is mostly notable on their borders and scroll bars.

Some of controls from Forms ActiveX have some drawbacks in combination with MediaMonkey, but I think that MM is guilty for this, not control itself. For example, you cannot navigate within listbox control with up and down arrow keys, because MM transfer focus to the next control like when you press Tab or Shift+Tab. I noticed strange behavior with the Textbox control when the cursor is changing between the default (pointer) shape and I-beam shape as you move the mouse over the text, but more ugly thing with the Textbox is its font setting which has an effect only on every second character (i.e. one character has specified font and the next character has default font). Fortunately, my controls don't have such issues.

Well, speaking of fonts, I come to the point when I should tell you why I think that my suggestion is even better than MM3 built-in multi-line edit control (without a fact that my suggestion works with MM2). You could guess - MM3 control don't have Font property. Well, this is not unexpected since none of MM built-in control don't have Font property. Their controls don't have Border property. Their controls don't have Background Color nor Foreground Color properties. Their multi-line edit control don't expose OnSelect event. Their multi-line edit control don't have SelectStart and SelectEnd properties. Well, neither their Edit control don't have those properties. MM developers think that we don't need to know which part of text is selected and where exactly is textual cursor, i.e. after which character. Somebody of them also should tell me why they are created a new control with a new name (Multilineedit). I would expect to get a new boolean property (Multiline) with the existing Edit control, like we have with Visual Basic and Forms Textbox controls, but not totally new control.

And now, most senseless thing about MM3 Multiline edit control - it has not scroll bars at all! Nor horizontal, nor vertical! Just type as much text as you want, but you could not get any scroll bar, neither with the new MM 3.1! Oh well. Even Visual Basic for DOS had text boxes with scroll bars. You know, with my control I could decide when and which scroll bars I want to get (overflow style).

OK, enough talk. If you don't want to go through 420 KB of source code of Magic Nodes script, here are those controls. If you are still wondering how they work, just to say that I am using WebBrowser ActiveX from Shell.Explorer which is ready to use without any registration or something similar. First, here is how you should create textbox:

Code: Select all

    Set edtFilter = SDB.UI.NewActiveX(Form, "Shell.Explorer")
    edtFilter.Common.SetRect 66, 209, 454, 50
    edtFilter.Common.Anchors = 1 + 2 + 4 + 8
    sTmp = "<html><head><style type='text/css'>" _
            & "body {margin: 0px 0px; overflow: hidden; border: none} " _
            & "textarea {font: 12px 'courier new' 'serif'; overflow: auto;" _
            & " margin: 0px 0px;"
    If bSkinned Then
        sTmp = sTmp & " border: 1px solid;"
    Else
        sTmp = sTmp & ""
    End If
    sTmp = sTmp & " width: 454px; height: 49px}</style></head>"
    sTmp = sTmp & "<script type='text/javascript'>var iKeyCode</script>" _
            & "<body" _
            & " onkeydown = 'iKeyCode = event.keyCode;'" _
            & " onkeyup = 'iKeyCode = """";'" _
            & " onfocusout = 'if(iKeyCode == 38 || iKeyCode == 40)" _
            & "  event.srcElement.focus();'"
    ' Previous statement is needed because of MM bug with the up/down keys
    ' which transfer focus to the other controls on the form when the textual
    ' cursor is on the top/bottom of this control.
    sTmp = sTmp & " onresize = '" _
            & "document.body.firstChild.style.posWidth " _
            & "= document.body.offsetWidth; " _
            & "document.body.firstChild.style.posHeight " _
            & "= document.body.offsetHeight - 1'"
    ' Previous statement is needed for textarea element to have same size
    ' as its parent.
    sTmp = sTmp & " onload = '" _
            & "document.body.firstChild.style.posWidth " _
            & "= document.body.offsetWidth; " _
            & "document.body.firstChild.style.posHeight " _
            & "= document.body.offsetHeight - 1'"
    ' Strange, onload = 'this.fireEvent(""onresize"");' doesn't work.
    sTmp = sTmp & " oncontextmenu = 'if(this.firstChild.disabled)return false;'"
    ' Pervious line is needed because the context menu is displayed by
    ' the right-click even when a control is disabled.
    sTmp = sTmp & "><textarea></textarea></body></html>"
    edtFilter.SetHTMLDocument sTmp
    Script.RegisterEvent edtFilter.Interf.document.body.firstChild, _
            "onpropertychange", "edtFilter_OnChange"
    Script.RegisterEvent edtFilter.Interf.document, "onselectionchange", _
            "edtFilter_OnSelect"
Many properties of this control like border and font could be set with the style element of the WebBrowser control as you could see from the previous code. Next, we could respond on events and then we have two functions to get/set textual value of this control:

Code: Select all

Sub edtFilter_OnSelect()
' ...
End Sub

Sub edtFilter_OnChange()
' ...
End Sub

Sub SetEditBoxText(oCtrl, sText)
    If Not oCtrl.Interf.document.getElementsByTagName("textarea").Item(0) _
            Is Nothing Then

        oCtrl.Interf.document _
                .getElementsByTagName("textarea").Item(0).innerText = sText
    End If
End Sub

Function GetEditBoxText(oCtrl)
    If Not oCtrl.Interf.document.getElementsByTagName("textarea").Item(0) _
            Is Nothing Then

        GetEditBoxText = oCtrl.Interf.document _
                .getElementsByTagName("textarea").Item(0).innerText
    End If
End Function
When you want to get text from control you should write GetEditBoxText(edtFilter) where edtFilter is a name of this control, and when you want to set text to control you should write SetEditBoxText edtFilter, "some text string". There is only one more thing which should be mentioned about this control - its Enabled state. I didn't create new procedure for this, instead I used just one line:
edtFilter.Interf.document.body.firstChild.disabled = ... Well, you could try with edtFilter.Common.Enabled = ... but you would get some strange results. I could also explain how I get/set selected part of text, but you should take a look on the Magic Nodes script to find this.

Now, about listbox control... First, here is how we could create it:

Code: Select all

    Set lstNodes = SDB.UI.NewActiveX(Form, "Shell.Explorer")
    lstNodes.Common.SetRect 254, 75, 155, 97
    lstNodes.Common.Anchors = 1 + 2 + 4
    sTmp = "<html><head><style type='text/css'>" _
            & "body {margin: 0px 0px; overflow: hidden; border: none} " _
            & "select {font: 11px 'tahoma' 'sans-serif'; margin: 0px 0px;" _
            & "</style></head><body"
    sTmp = sTmp & " onresize = '" _
            & "document.body.firstChild.style.posWidth " _
            & "= document.body.offsetWidth; " _
            & "document.body.firstChild.style.posHeight " _
            & "= document.body.offsetHeight'"
    ' Previous statement is needed for select element to have same size
    ' as its parent.
    sTmp = sTmp & " onload = '" _
            & "document.body.firstChild.style.posWidth " _
            & "= document.body.offsetWidth; " _
            & "document.body.firstChild.style.posHeight " _
            & "= document.body.offsetHeight'"
    ' Strange, onload = 'this.fireEvent(""onresize"");' doesn't work.
    sTmp = sTmp & "><select size = '100'" ' Setting for listboxes
    sTmp = sTmp & " onmousedown = 'this.focus()'" ' Because of skinned MM bug
    sTmp = sTmp & "></select></body></html>"
    lstNodes.SetHTMLDocument sTmp
    Script.RegisterEvent lstNodes.Interf.document.body.firstChild, _
            "onchange", "lstNodes_OnChange"
Then we have event handler and several functions to get/set the selected row in this listbox and its text value, as well as to get the number of rows and to add/insert/remove some row:

Code: Select all

Sub lstNodes_OnChange()
' ...
End Sub

Sub ListBoxAddItem(oCtrl, sText)
    Dim oSelect, oOption, bDisabled

    Set oSelect = oCtrl.Interf.document.getElementsByTagName("select").Item(0)
    Set oOption = oCtrl.Interf.document.createElement("option")
    If Not oSelect.disabled Then
        oSelect.disabled = True
        ' Really stupid thing! This workaround with temporary disabling
        ' the select element is because adding the option to this element
        ' takes the focus from the another active control!!!
    Else
        bDisabled = True
    End If
    oSelect.add oOption
    If Not bDisabled Then oSelect.disabled = False
    oOption.text = sText
End Sub

Sub ListBoxInsertItem(oCtrl, sText, iIndex)
    Dim oSelect, oOption, bDisabled

    Set oSelect = oCtrl.Interf.document.getElementsByTagName("select").Item(0)
    If iIndex < oSelect.length Then
        Set oOption = oCtrl.Interf.document.createElement("option")
        If Not oSelect.disabled Then
            oSelect.disabled = True
        Else
            bDisabled = True
        End If
        oSelect.options.add oOption, iIndex + 1
        If Not bDisabled Then oSelect.disabled = False
        oOption.text = sText
    End If
End Sub

Sub ListBoxRemoveItem(oCtrl, iIndex)
    oCtrl.Interf.document.getElementsByTagName("select").Item(0).remove iIndex
End Sub

Function ListBoxListCount(oCtrl)
    ListBoxListCount = oCtrl.Interf.document.getElementsByTagName("select") _
            .Item(0).length
End Function

Sub SetListBoxListIndex(oCtrl, iIndex)
    oCtrl.Interf.document.getElementsByTagName("select") _
            .Item(0).selectedIndex = iIndex
End Sub

Function GetListBoxListIndex(oCtrl)
     GetListBoxListIndex = oCtrl.Interf.document.getElementsByTagName("select") _
            .Item(0).selectedIndex
End Function

Sub SetListBoxListText(oCtrl, iIndex, sText)
    Dim oSelect

    Set oSelect = oCtrl.Interf.document.getElementsByTagName("select").Item(0)
    If Not oSelect Is Nothing And iIndex >= 0 Then
        If iIndex < oSelect.length Then
            oSelect.options(iIndex).text = sText
        End If
    End If
End Sub

Function GetListBoxListText(oCtrl, iIndex)
    Dim oSelect

    Set oSelect = oCtrl.Interf.document.getElementsByTagName("select").Item(0)
    If Not oSelect Is Nothing And iIndex >= 0 Then
        If iIndex < oSelect.length Then
            GetListBoxListText = oSelect.options(iIndex).text
        End If
    End If
End Function
Similar as with textbox control, you should use following line to enable/disable listbox control: lstNodes.Interf.document.body.firstChild.disabled = ... Unfortunately, with the listbox it is not easy task to change its border. You could try to set its margins to some negative value (e.g. -2px) to hide default border or something similar, but I left it like it is.

On the end of this post I am presenting one complete function (in fact two) which could be used as a multi-line replacement for a single-lined SkinnedInputBox window which could be seen in some MM scripts:

Code: Select all

' Some necessary global variables:
Dim sInputText
Dim edtInput

Function MultilineInputBox(sLabel, sCaption, sEditText)
    Dim oForm
    Dim lblCtrl
    Dim btnOK
    Dim btnCancel
    Dim iBorderWidth
    Dim iBorderHeight
    Dim sTmp

    Set oForm = SDB.UI.NewForm
    oForm.BorderStyle = 2    ' Resizable
    iBorderWidth = oForm.Common.Width - oForm.Common.ClientWidth
    iBorderHeight = oForm.Common.Height - oForm.Common.ClientHeight
    oForm.Common.SetRect 100, 100, 515 + iBorderWidth, 445 + iBorderHeight
    oForm.Common.MinWidth = 365 + iBorderWidth
    oForm.Common.MinHeight = 95 +  + iBorderHeight
    oForm.FormPosition = 4   ' Screen Center
    oForm.Caption = sCaption

    Set lblCtrl = SDB.UI.NewLabel(oForm)
    lblCtrl.Caption = sLabel
    lblCtrl.Common.Left = 10
    lblCtrl.Common.Top = 10

    Set edtInput = SDB.UI.NewActiveX(oForm, "Shell.Explorer")
    edtInput.Common.SetRect 10, 30, 493, 375
    edtInput.Common.Anchors = 1 + 2 + 4 + 8
    sTmp = "<html"
    sTmp = sTmp & "><head><style type='text/css'>" _
            & "body {margin: 0px 0px; overflow: hidden; border: none} " _
            & "textarea {font: 12px 'courier new' 'serif'; overflow: auto;" _
            & " margin: 0px 0px;}</style></head>"
    sTmp = sTmp & "<script type='text/javascript'>var iKeyCode</script>" _
            & "<body" _
            & " onkeydown = 'iKeyCode = event.keyCode;'" _
            & " onkeyup = 'iKeyCode = """";'" _
            & " onfocusout = 'if(iKeyCode == 38 || iKeyCode == 40)" _
            & "  event.srcElement.focus();'"
    ' Previous statement is needed because of MM bug with the up/down keys
    ' which transfer focus to the other controls on the form when the textual
    ' cursor is on the top/bottom of this control.
    sTmp = sTmp & " onresize = '" _
            & "document.body.firstChild.style.posWidth " _
            & "= document.body.offsetWidth; " _
            & "document.body.firstChild.style.posHeight " _
            & "= document.body.offsetHeight - 1'"
    ' Previous statement is needed for textarea element to have same size
    ' as its parent.
    sTmp = sTmp & " onload = '" _
            & "document.body.firstChild.style.posWidth " _
            & "= document.body.offsetWidth; " _
            & "document.body.firstChild.style.posHeight " _
            & "= document.body.offsetHeight - 1'"
    ' Previous statement is needed because of non-skinned version which doesn't
    ' raise the onresize event when opening dialog.
    ' Strange, onload = 'this.fireEvent(""onresize"");' doesn't work.
    sTmp = sTmp & "><textarea>" & sEditText & "</textarea></body></html>"
    edtInput.SetHTMLDocument sTmp
    Script.RegisterEvent edtInput.Interf.document.body.firstChild, _
            "onpropertychange", "edtInput_OnChange"
    Set SDB.Objects("MNedtInput") = edtInput
    sInputText = sEditText

    Set btnOK = SDB.UI.NewButton(oForm)
    btnOK.Caption = "&OK"
    btnOK.Common.SetRect 350, 413, 73, 24
    btnOK.Common.Anchors = 4 + 8
    btnOK.UseScript = Script.ScriptPath
    btnOK.Default = True
    btnOK.Common.Enabled = False
    btnOK.ModalResult = 1
    Set SDB.Objects("btnOKInput") = btnOK

    Set btnCancel = SDB.UI.NewButton(oForm)
    btnCancel.Caption = "&Cancel"
    btnCancel.Common.SetRect 430, 413, 73, 24
    btnCancel.Common.Anchors = 4 + 8
    btnCancel.UseScript = Script.ScriptPath
    btnCancel.Cancel = True
    btnCancel.modalResult = 2

    oForm.SavePositionName = "Multiline InputBox"
    If oForm.showModal = 1 Then
        MultilineInputBox = sInputText
    Else
        MultilineInputBox = sEditText
    End If
    Set SDB.Objects("btnOKInput") = Nothing
End Function

Sub edtInput_OnChange()
    sInputText = GetEditBoxText(edtInput)
End Sub
If you find this code examples useful and want to include them in your source code, please be kind and mention my name. I spent significant part of time to discover all of this which is presented here.
Magic Nodes 4.3.3 / 5.2 RegExp Find & Replace 4.4.9 / 5.2  Invert Selection/Select None 1.5.1  Export/Create Playlists for Child Nodes 4.1 / 5.4  Expand Child Nodes/Expand All 1.1.2  Event Logger 2.7  Filtered Statistics Report 1.6  Track Redirection & Synchronization 3.4.2  Restore/Synchronize Database 3.1.7 / 4.0  Find Currently Playing Track 1.3.2  Queue List 1.2.1  Add to Library on Play 1.0.1  Tree Report for Child Nodes 1.1.1  Update Location of Files in Database 1.4.3 / 2.2.2  Inherit Child Playlists 1.0.2  Add Currently Playing/Selected Track(s) to Playlist 1.1.2

Bex
Posts: 6316
Joined: Fri May 21, 2004 5:44 am
Location: Sweden

Re: MultilineInputBox (listbox & multilineedit examples) [MM2+3]

Post by Bex » Sun Dec 14, 2008 5:36 pm

Thank you for this. I think I'll use them in some of my script to make them more user friendly. I will of course acknowledge you when I do.
Advanced Duplicate Find & Fix Find More From Same - Custom Search. | Transfer PlayStat & Copy-Paste Tags/AlbumArt between any tracks.
Tagging Inconsistencies Do you think you have your tags in order? Think again...
Play History & Stats Node Like having your Last-FM account stored locally, but more advanced.
Case & Leading Zero Fixer Works on filenames too!

All My Scripts

MoDementia
Posts: 1321
Joined: Thu Jun 15, 2006 3:26 pm
Location: Geelong, Victoria, Australia

Re: MultilineInputBox (listbox & multilineedit examples) [MM2+3]

Post by MoDementia » Mon Dec 15, 2008 1:35 am

MultilineInputBox (listbox & multilineedit examples) [MM2+3]
If I knew what they looked like I could confirm or deny existing code :P

ZvezdanD
Posts: 3090
Joined: Thu Jun 08, 2006 7:40 pm

Re: MultilineInputBox (listbox & multilineedit examples) [MM2+3]

Post by ZvezdanD » Tue Jan 20, 2009 8:30 am

I am currently trying to improve look of my scripts when used with some skins. Listboxes and multilineedit controls which I have used so far with a help of the Web Browser control are not very well with some skins, especially dark ones. So, I tried to replace them with the newly introduced MM built-in controls. Well, I think I will stay with my controls :(. I will not tell you why I wan't use their listboxes, but I will show you a reason why I don't want to use their updated MultilineEdit control. First of all, they are added SelStart and SelLength properies (which is great), but they are not added OnSelect event. Maybe I could simulate this with OnKeyPress, OnMouseDown... if such events exist. Unfortunately, such events don't exist for any built-in control :( Also, I'd like to see WordWrap property - I don't like to have horizontal scroll bar at all.

Now, about look... They are added Font properties (which is great), even the scroll bars, but just take a look on this screenshot:

Image

What we have here? First, scroll bars are not skinned, colors are totally different than current skin, white background instead black, foreground color, .... Now, pay attention on its border. I'd like if somebody tell me in which program could be seen the multilined textbox which have a border only around the client area. Every textbox control which I saw until now had border around the client area AND scroll bars. Even this textarea where I am now typing this text has a border around client area INCLUDING scroll bars. But, this is not a case with their multilined panels, even their built-in ones - just take a look at Properties dialog box and Comment/Lyrics fields. I say - no, thanks.
Magic Nodes 4.3.3 / 5.2 RegExp Find & Replace 4.4.9 / 5.2  Invert Selection/Select None 1.5.1  Export/Create Playlists for Child Nodes 4.1 / 5.4  Expand Child Nodes/Expand All 1.1.2  Event Logger 2.7  Filtered Statistics Report 1.6  Track Redirection & Synchronization 3.4.2  Restore/Synchronize Database 3.1.7 / 4.0  Find Currently Playing Track 1.3.2  Queue List 1.2.1  Add to Library on Play 1.0.1  Tree Report for Child Nodes 1.1.1  Update Location of Files in Database 1.4.3 / 2.2.2  Inherit Child Playlists 1.0.2  Add Currently Playing/Selected Track(s) to Playlist 1.1.2

Post Reply