PickIconDlg won't allow path longer than initial path setting - vb.net

I need to allow the user to select an icon, so I've implemented the PickIconDlg function from shell32. The problem is that if the user selects a path that is longer than the initial path I declare, the resultant value is the user-selected path truncated to length of the initial path.
For instance, if I set the initial path to "C:\Windows\System32\shell32.dll" and the user selects "C:\Users\Public\Documents\TheIcons\Library.dll", the updated string value comes back as "C:\Users\Public\Documents\TheIc" (i.e. the first 31 characters of the user-selected path, because the initial path is 31 characters long).
I have tried adjusting the 'nMaxFile' value I pass to PickIconDlg, which I understand is supposed to set the max length of the path variable. This doesn't seem to make a difference.
Declare Unicode Function PickIconDlg Lib "Shell32" Alias "PickIconDlg" (ByVal hwndOwner As IntPtr, ByVal lpstrFile As String, ByVal nMaxFile As Integer, ByRef lpdwIconIndex As Integer) As Integer
Public Function GetIconLoc(frmform As Form) As Object()
Dim iconfile As String = Environment.GetFolderPath(Environment.SpecialFolder.System) + "\shell32.dll"
Dim iconindex As Integer ' Will store the index of the selected icon
If PickIconDlg(frmform.Handle, iconfile, 50, iconindex) = 1 Then
MessageBox.Show(iconfile)
Return {iconfile, iconindex}
Else
Return Nothing
End If
End Function
I expect the string variable iconfile to contain the full user-selected path, as its length is less than the defined max of 50 characters. Instead, only a portion of the path is returned, as described above.

Append a Null character plus empty space after the original file name to create a string buffer large enough to contain the result.
Dim iconfile As String = _
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "shell32.dll") & _
vbNullChar & Space(256)
Then pass the total length when calling the function
PickIconDlg(frmform.Handle, iconfile, Len(iconfile), iconindex)
And finally cut the extra space from the result
iconfile = Left(iconfile, InStr(iconfile, vbNullChar) - 1)
Also, use Path.Combine instead of concatenating the path yourself. Path.Combine adds missing or removes extra backslashes (\) automatically.

Related

Why does GetPrivateProfileSection retrieve each character as a two byte value, padding them with a NULL character?

Given this piece of code:
Private Declare Auto Function GetPrivateProfileSection Lib "kernel32" _
(ByVal lpAppName As String, _
ByVal lpszReturnBuffer As Byte(), _
ByVal nSize As Integer, ByVal lpFileName As String) As Integer
Public Class IniClassReader
Public Function readWholeSection(iniFile as String, section as String) as String()
Dim buffer As Byte() = New Byte(SECTIONLENGTH) {}
GetPrivateProfileSection(section, buffer, SECTIONLENGTH, iniFile)
Dim sectionContent As String = Encoding.Default.GetString(buffer)
' Skipped code embedded in the function below, not the point of the question
return processSectionContent(sectionContent)
End Function
End Class
I figured out that buffer contains a sequence of bytes interspersed with NULL characters (\0). Hence, sectionContent value is seen by the spying variable feature as 'e n t r i e 1 = v a l u e 1 e n t r i e 2 = v a l u e 2'. Each pair key/value is as expected followed by two NULL characters instead of one.
I don't see why each character is stored as a two byte value. Replacing Default by UTF8 gives the same result. I tried with a INI file encoded in UTF8 and Windows-1252 (so called "ANSI" by Microsoft).
I know how to get ride of those extra bytes:
Dim sectionContent As String = Encoding.Default.GetString(buffer)
sectionContent = sectionContent.Replace(Chr(0) & Chr(0), vbNewLine).Replace(Chr(0), "")
But I want to understand what's going on here to apply the best solution instead of some sloppy hack working only on some cases.
The bytes are UTF-16 encoded text. It looks like null character padding because all of your text consists of characters whose encodings fit in the low byte.
The Windows API exposes both an "A" and a "W" version of the function, with the "A" version working in narrow strings and the "W" version working in wide strings. The default for the Windows NT family tree (thus all Windows since XP) is wide as UCS-2/UTF-16 is the "native" Windows character encoding.

Comparing strings not working properly vb.net

I'm trying to compare 2 strings (KeyCode and SerialN)
I've tried a bunch of diferente aproaches... but as you can see in my print screen neither = or Equals work.
Can you tell me what is wrong?
Private Function VerificaKeyCode(SerialN As String) As Boolean
Dim snEsq As Integer
Dim snDir As Integer
Dim KeyCode As String
Dim buffer As String
Dim ind As Short
VerificaKeyCode = False
buffer = Space(255)
For ind = 1 To 5
'UPGRADE_WARNING: App property App.EXEName has a new behavior. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6BA9B8D2-2A32-4B6E-8D36-44949974A5B4"'
GetPrivateProfileString(My.Application.Info.AssemblyName, "HD" & ind, "", buffer, Len(buffer), My.Application.Info.DirectoryPath & "\ServerHD.ini")
KeyCode = Trim(buffer)
If KeyCode <> "" Then
VerificaKeyCode = KeyCode.Equals(SerialN)
VerificaKeyCode = CBool(KeyCode = SerialN)
If VerificaKeyCode Then Exit Function
End If
Next ind
End Function
Edit
Aparently there is an empty char in the string, I need to remove it somehow
The expressions in your watch window are stale as indicated by the disabled color and the presence of the Refresh/Recycle icon. Click the icon and the expression will be re-evaluated and the result updated:
Mimicking what you have, notice the first expression is stale and wrong. The second has been refresh/reevaluated and reports the correct result. As soon as you execute a line of code, the watch expressions will be marked as stale/not updated.
A better way to return a function with NET is to use a local variable and Return it. The local var allow you to easily view the result by hovering the mouse:
A variable can go out of scope, but it can't be stale.
Edit 4 or 5 reveals that while the watch expressions are stale, so what we see in the pic neither proves nor disproves that the strings do not match, this is apparently a VB6 -> .NET refurb (notice the 102 warnings).
We cant see the declaration for GetPrivateProfileString but the usage is at least suboptimal.
<DllImport("kernel32.dll", SetLastError:=True)>
Shared Function GetPrivateProfileString(appname As String,
keyname As String, def As String,
sbuffer As StringBuilder,
nSize As Int32,
strFile As String) As Integer
End Function
Note that it is a Function; it returns the number of chars read into the buffer. Rather than Trim which will not remove NUL, the return should be used (if not a stringbuilder) to get the number of characters specified. If you use a string buffer, trim to the number of characters read:
Dim n As Int32 = GetPrivateProfileString(...)
KeyCode = buffer.Substring(0, n)
StringBuilder should do that for you:
Dim buffer As New StringBuilder(255)
...
Dim n As Int32 = GetPrivateProfileString(..., buffer, buffer.Capacity)
KeyCode = buffer.ToString()
You can still check the function return against the size of the string.
You could skip GetPrivateProfileString entirely and read the file yourself with a stream reader. It should be faster than PInvoke, but at the least, less convoluted to use.

Fitting the full File path in a label in one line by showing dots instead of some part of file path Vb.Net

Hi Guys, I have searched for different methods for this thing but couldn't get it right. Used AutoEllipses Property, Set the MaximumSize too but to no avail. How can i get a label to show the name of the file being scanned like in the pic i attached? I mean, the label should show some part from the beginning of full path of the file then some dots and then the file name with extension.
You might consider a few things; however, the range of possibilities is too large to cover, here.
There are three (3) things you need to know in order to code this properly: the actual measured size of the filepath string you are sending to the label; the measured size of the label; and the length (in characters) of your file name. There may be a fancy function that reduces the number of things you need to do and know; however, I am not about to read oodles of documentation.
All of the above things need to be dynamic so that your label can take different String objects and render them, properly.
Dim filePath As String = ""
Dim FileDirectory As String = ""
Dim fileName As String = ""
Dim filePathLength As SizeF = 0.0
Dim labelLength As Double = 0.0
Dim fileNameLength As Integer = 0.0
' Come up with a way for measuring your string:
Dim _GraphicsUnit As Graphics = Me.CreateGraphics()
' Receive your file path, here:
' and work with your file path-related Strings:
filePath = ' SOMETHING
fileDirectory = Path.GetDirectoryName(filePath)
fileName = Path.GetFileName(filePath)
fileNameLength = fileName.Length()
' Measure the length of you path
filePathLength = _GraphicsUnit.MeasureString(filePath, INSERTFONT) * _GraphicsUnit.Inches 'or other usable unit
If filePathLength > SIZEOFLABEL Then
While filePathLength > SIZEOFLABEL
' Grab a substring of the the fileDirecory, append the "...", and keep measuring until shorter
' than SIZEOFLABEL.
' Your algorithm will need to figure out how and when to re-append the fileName
End While
End If
The above is pseudo-code and is rife with errors. The above is means to demonstrate some of the tools .Net can provide you, here namely the GraphicsUnit stuff and the Path. stuff. Both of those are helpful. You will essentially be juggling those two 'things' and the SubString() method.
My attempt is to show you how to begin to think about the problem you have in front of you so that you can begin to tackle the problem (because as the comments above state, there isn't much out there that will do what you need). Your initial question does not provide any original code on which to base the above pseudo-code; in other words, I don't feel like coding your whole project but at least want to get the answers ball rolling.
An Additional Thought: .MaxLength
The above approach is quite memory intensive - requiring a lot of repetition that may not be be necessary. Simply knowing the size - in this case the MaxLength property - might be helpful. Setting the .MaxLength property of the TextBox will allow you to know how many characters can fit in the box (you'd need to consider a few other elements, e.g. font, size, etc.).
Knowing this number, you could avoid looping altogether:
SubString of fileDirectory equal to the length of .MaxLength property, remove number of characters equating to size of fileName and "..." and append the latter two.
I got an answer to this problem here Shorten The File Path and it's a very short solution as far as code is concerned.
You can use the PathCompactPathExW pInvoke method to accomplish this:
Imports System.Runtime.InteropServices
Imports System.Text
Public Class Program
<DllImport("shlwapi.dll", EntryPoint:="PathCompactPathExW", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Public Shared Function PathCompactPathEx(<MarshalAs(UnmanagedType.LPTStr)> pszOut As System.Text.StringBuilder, _
<MarshalAs(UnmanagedType.LPTStr)> pszSrc As String, _
cchMax As UInteger, _
reserved As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Public Shared Sub Main()
Dim longPath As String = "c:\a\very\very\long\path\that\needs\to\be\shortened\by\calling\the\PathCompactpathEx.ext"
Dim length As Integer = 40
Dim result As String = CompactPath(longPath, length)
'Prints c:\a\very\very\...\PathCompactpathEx.ext
Console.WriteLine(result)
Console.ReadLine()
End Sub
Public Shared Function CompactPath(longPathName As String, wantedLength As Integer) As String
'NOTE: You need to create the builder with the required capacity before calling function.
'See http://msdn.microsoft.com/en-us/library/aa446536.aspx
Dim sb As New StringBuilder(wantedLength + 1)
PathCompactPathEx(sb, longPathName, CUInt(wantedLength + 1), 0)
Return sb.ToString()
End Function
End Class

how to read all strings that are between 2 other strings

i have managed to find a string between 2 specified strings,
the only issue now is that it will only find one and then stop.
how am i possible to make it grab all the strings in a textbox?
the textbox is multiline and i have put a litle config in it.
now i want that the listbox will add all the strings that are between my 2 specified strings.
textbox3.text containts "<"
and textbox 4.text contains ">"
Public Function GetClosedText(ByVal source As String, ByVal opener As String, ByVal closer As String) As String
Dim intStart As Integer = InStr(source, opener)
If intStart > 0 Then
Dim intStop As Integer = InStr(intStart + Len(opener), source, closer)
If intStop > 0 Then
Try
Dim value As String = source.Substring(intStart + Len(opener) - 1, intStop - intStart - Len(opener))
Return value
Catch ex As Exception
Return ""
End Try
End If
End If
Return ""
End Function
usage:
ListBox1.Items.Add(GetClosedText(TextBox1.Text, TextBox3.Text, TextBox4.Text))
The easiest way (least lines of code) to do this would be to use a regular expression. For instance, to find all of that strings enclosed in pointy brackets, you could use this regular expression:
\<(?<value>.*?)\>
Here's what that all means:
\< - Find a string which starts with a < character. Since < has a special meaning in RegEx, it must be escaped (i.e. preceded with a backslash)
(?<value>xxx) - This creates a named group so that we can later access this portion of the matched string by the name "value". Everything contained in the name group (i.e. where xxx is), is considered part of that group.
.*? - This means find any number of any characters, up to, but not including whatever comes next. The . is a wildcard which means any character. The * means any number of times. The ? makes it non-greedy so it stops matching as soon as if finds whatever comes next (the closing >).
\> - Specifies that matching strings must end with a > character. Since > has a special meaning in RegEx, it must also be escaped.
You could use that RegEx expression to find all the matches, like this:
Dim items As New List(Of String)()
For Each i As Match In Regex.Matches(source, "\<(?<value>.*?)\>")
items.Add(i.Groups("value").Value)
Next
The trick to making it work in your scenario is that you need to dynamically specify the opening and closing characters. You can do that by concatenating them to the RegEx, like this:
Regex.Matches(source, opener & "(?<value>.*?)" & closer)
But the problem is, that will only work if source and closer are not special RegEx characters. In your example, they are < and >, which are special characters, so they need to be escaped. The safe way to do that is to use the Regex.Escape method, which only escapes the string if it needs to be:
Private Function GetClosedText(source As String, opener As String, closer As String) As String()
Dim items As New List(Of String)()
For Each i As Match In Regex.Matches(source, Regex.Escape(opener) & "(?<value>.*?)" & Regex.Escape(closer))
items.Add(i.Groups("value").Value)
Next
Return items.ToArray()
End Function
Notice that in the above example, rather than finding a single item and returning it, I changed the GetClosedText function to return an array of strings. So now, you can call it like this:
ListBox1.Items.AddRange(GetClosedText(TextBox1.Text, TextBox3.Text, TextBox4.Text))
I ssume you want to loop all openers and closers:
' always use meaningful variable/control names instead of
If TextBox3.Lines.Length <> TextBox4.Lines.Length Then
MessageBox.Show("Please provide the same count of openers and closers!")
Return
End If
Dim longText = TextBox1.Text
For i As Int32 = 0 To TextBox3.Lines.Length - 1
Dim opener = TextBox3.Lines(i)
Dim closer = TextBox4.Lines(i)
listBox1.Items.Add(GetClosedText(longText, opener , closer))
Next
However, you should use .NET methods as shown here:
Public Function GetClosedText(ByVal source As String, ByVal opener As String, ByVal closer As String) As String
Dim indexOfOpener = source.IndexOf(opener)
Dim result As String = ""
If indexOfOpener >= 0 Then ' default is -1 and indices start with 0
indexOfOpener += opener.Length ' now look behind the opener
Dim indexOfCloser = source.IndexOf(closer, indexOfOpener)
If indexOfCloser >= 0 Then
result = source.Substring(indexOfOpener, indexOfCloser - indexOfOpener)
Else
result = source.Substring(indexOfOpener) ' takes the rest behind the opener
End If
End If
Return result
End Function

VB.Net Parse/Replace a substring in a line

I know this should be simple yet I am a little stuck. I am reading in a text file line by line. Each line is formated the same based off an ICD. I need to take the data at a specific location and replace it with x's.
For Example:
Line = "First Name Last Name Street Address State ZIP Other Data"
This is a fixed length ICD so address always starts at lets say position 100 and goes through 150
I need to replace everything position 100 to 150 with x's.
From there I am writing the line out to a new file and that part is working fine.
Thank you so much for your help.
Use this:
Dim newLine As String = Line.Substring(0, 100) & New String("x"c, 50) & line.Substring(150)
You can create a function that takes in the string, start index, and length and returns the string with the replaced characters. This will also handle error cases where the length is greater than string length (in which case the rest of the string is replaced with the char you've chosen).
Private Shared Function ReplaceCharsWithChar(input As String, firstIndex As Integer, length As Integer, replaceChar As Char) As String
Dim sb As New StringBuilder(input)
For i As Integer = firstIndex To Math.Min(firstIndex + length, input.Length) - 1
sb(i) = replaceChar
Next
Return sb.ToString()
End Function
And call it like this
Dim input As String = "First Name Last Name Street Address State ZIP Other Data"
Dim result As String = ReplaceCharsWithChar(input, 10, 5, "x"C)
'output would be First Namexxxxx Name Street Address State ZIP Other Data
There is no built-in method to do that, so you'll need to implement it yourself. The simplest way would be to use the String.Substring method to extract the parts you want (the beginning and ending of the string), and then concatenate them back together with the replacement value. For instance:
Dim newValue As String = line.Substring(0, 99) & New String("X"c, 50) & line.Substring(150)
However, if you need to replace more than one section of the string, it may be easier and more efficient to use the StringBuilder, which allows you to manipulate each character in place:
Dim builder As New StringBuilder(line)
For i As Integer = 100 to 149
builder.Chars(i) = "X"c
Next
line = builder.ToString()