Changing control (that is in an array) from a timer: Not working - arrays

I'm trying to make a picture's visibility toggle on a timer. The picture I am trying to change is underscore(i). Here is that code:
Dim DigitSelected As Integer = 1
Public Underscores(3) As PictureBox
....
Private Sub CursorTimer_Tick(sender As System.Object, e As System.EventArgs) Handles CursorTimer.Tick
Me.Underscores(DigitSelected).Visible = Not (Me.Underscores(DigitSelected).Visible)
End Sub
This code above was previously working, but recently I moved where I was creating the pictureboxes and their stuff to a module on another page:
With Initials
For i As Byte = 1 To 3
.Underscores(i) = New PictureBox
With .Underscores(i)
.Height = 60
.Width = 144
.ImageLocation = "Underscore.png"
.BackColor = Color.Transparent
End With
.Controls.Add(.Underscores(i))
Next
end with
Now when I use the top-most snippet of code, it throws no errors, and changes nothing. I'm pretty sure I'm just missing something small.
Any ideas? Thanks!

I don't exactly know what you are trying to achieve here, but here is what happens :
Everytime you run that loop, you create three transparent PictureBox that you add to your Main Form. You don't remove anything, nor hide anything !
If you want to toggle visibility, that is no way of doing so !
The best would be for you to know the PictureBoxes names, so you can do :
Dim pb1 = CType(Initials.FindControl("MyPictureBox", true), PictureBox)
If Not IsNothing(pb1) Then
pb1.Visible = Not pb1.Visible
End If

Related

WPF VB.Net ProgressBar updating in an Async Method

I've seen a few other posts about this but I seem to be confused since I've seen it done several ways and haven't gotten it correct any of the ways, so I thought I would ask specifically for my case so I can learn what I am doing wrong. After learning and switching to C# for most of my programming, VB.net seems so clunky in its syntax with a lot of things, especially lambda and "on-the-fly" functions.
I have a long running task for a football game I am working on where it generates players. Its an async method utilizing the Task.Factory.StartNew method.
Here is the pertinent code:
Private Sub CreateDraft_Click(sender As Object, e As RoutedEventArgs) Handles CreateDraft.Click
TimeIt(Sub() ReallyGenNewPlayers())
End Sub
'Uses a stopwatch to time how long it takes and sends to console.
Private Sub TimeIt(MyAction As Action)
Dim SW As New Stopwatch
SW.Start()
MyAction()
Console.WriteLine($"Total Time Generating Players: {SW.Elapsed} seconds")
SW.Stop()
End Sub
Private Async Sub GenNewPlayersASync()
Dim myvalue = 0
'Generate the Players on an Async Thread
Dim x As Integer
For i As Integer = 1 To NumPlayers
x = i 'avoids issue with using the iteration variable inside the delegate
CollegePlayers.GenDraftPlayers(i, MyDraft, DraftDT, DraftClass, PosCount)
'Prog_ValueChanged(DBNull.Value, New RoutedPropertyChangedEventArgs(Of Double))
'Calls a delegate to update the progress bar to avoid having the variable locked by the background thread
Dispatcher.Invoke(Sub()
worker.ReportProgress((x / NumPlayers) * 100)
End Sub)
Next i
End Sub
'Creates a task to run the player generation code and wait til its finished
Private Sub ReallyGenNewPlayers()
Dim mytask = Task.Factory.StartNew(Sub() GenNewPlayersASync())
Task.WaitAll(mytask)
End Sub
So here is what I would like to do:
I have a progressbar I created in XAML that has a Progress_Changed Event. This is what I have for it so far based on another post, but the issue is when I have to call the function inside GenNewPlayersAsync() where it wants a RoutedPropertyChangeEventArgs as a double which I'm not exactly sure what to do...I tried creating one using .New(old value, new value) but that didn't work either and threw an error.
Public Async Sub Prog_ValueChanged(sender As Object, e As RoutedPropertyChangedEventArgs(Of Double))
Dim progress = New Progress(Of Integer)(Function(percent)
Prog.Value = e.NewValue
Return Prog.Value
End Function)
Await Task.Run(Sub() DoProcessing(progress))
End Sub
Public Sub DoProcessing(progress As IProgress(Of Integer))
Dim i As Integer = 0
While i <> 100
Thread.Sleep(100)
' CPU-bound work
If progress IsNot Nothing Then
progress.Report(i)
End If
End While
End Sub
I would like for the progressbar to be bound to an INotifyChanged Property and update itself automatically when the value gets changed. None of it seems to be working. The UI is still unresponsive and when I set different parts to ASync, I start getting Exceptions where it appears certain parts of the generation algorithm aren't working as they are returning null...very confused with all of this, and I am sure the answer is probably pretty simple...
If you guys could give several examples of different ways to get this to work so I can learn different methods and maybe state the pros and cons of the method I would greatly appreciate it...

Control Array Examples Needed for Visual Studio 2013

I know control arrays don't actually exist anymore, but I need something I can relate to my code. I'm making a shopping list game with a grid of 32 tiles that flip when clicked. They are actually PictureBoxes called pbxTile1 - pbxTile32. I sense you already know what I'm going to say.
A sample of my code:
Private Sub pbxTile1_Click(sender As Object, e As EventArgs) Handles pbxTile1.Click
If TileFlag(1) = 0 Then Exit Sub
My.Computer.Audio.Play(My.Resources.Tile_Flip, AudioPlayMode.Background) : Application.DoEvents()
Me.pbxTile1.BackgroundImageLayout = ImageLayout.Stretch
Me.pbxTile1.BackgroundImage = My.Resources.FLIP01 : Application.DoEvents() : System.Threading.Thread.Sleep(50)
Me.pbxTile1.BackgroundImage = My.Resources.FLIP02 : Application.DoEvents() : System.Threading.Thread.Sleep(50)
Me.pbxTile1.BackgroundImage = My.Resources.FLIP03 : Application.DoEvents() : System.Threading.Thread.Sleep(50)
Dim GroceryValue = TileItem(1)
Call Get_Grocery(GroceryValue)
Me.pbxTile1.BackgroundImageLayout = ImageLayout.None
Me.pbxTile1.BackgroundImage = My.Resources.ResourceManager.GetObject(GroceryResource) : Application.DoEvents()
You can see my problem - this is a fraction of the subroutine, which I need to recreate 32 times. But I'm sure one of you bright lads can come up with something to make it much less painful for me! I've seen tagging, and lists, and indexing - but not sure how to apply it, which is best, and need some examples please!
It's ok, I've found it! My word it works perfectly:
I didn't realise that Event Handlers can handle multiple Controls!
Instead of duplicating my code (32 times!) I just changed the Sub to:
Private Sub pbxTile_Click(ByVal sender As Object, e As System.EventArgs) Handles pbxTile1.Click, pbxTile2.Click, pbxTile3.Click, pbxTile4.Click, pbxTile5.Click, pbxTile6.Click, _
pbxTile7.Click, pbxTile8.Click, pbxTile9.Click, pbxTile10.Click, pbxTile11.Click, pbxTile12.Click, pbxTile13.Click, pbxTile14.Click, pbxTile15.Click, pbxTile16.Click, _
pbxTile17.Click, pbxTile18.Click, pbxTile19.Click, pbxTile20.Click, pbxTile21.Click, pbxTile22.Click, pbxTile23.Click, pbxTile24.Click, pbxTile25.Click, pbxTile26.Click, _
pbxTile27.Click, pbxTile28.Click, pbxTile29.Click, pbxTile30.Click, pbxTile31.Click, pbxTile32.Click
So basically if any of the 32 boxes are clicked, it calls the same Sub. And to differentiate between each PictureBox (this is the bit I was REALLY stuck on) I used DirectCast:
For z = 1 To 32
If DirectCast(sender, PictureBox).Name = "pbxTile" & z And TileFlag(z) = 0 Then Exit Sub
Next
I'm not sure if this is the most streamlined way, but it certainly works a treat, and saved me a bunch of code I didn't need!

Tying ComboBox/NumericUpDown selections to an array?

I'm working on a VB project that has a lot of comboboxes and numericupdown items.
Lets say we have ComboBox1, 2, 3, 4, and 5; and we have NumericUpDown1, 2, 3, 4, 5.
When the user clicks the "Save" button, I want to save all of their selected combobox items and numericupdown numbers to a CSV file. Is there an elegant/automatic way to tie all of the .SelectedIndex and .Value for these items to an array so I can easily write the array out to a CSV?
The only way I know to do this so far is to manually associate each one with an array position:
Arr(0) = ComboBox1.SelectedIndex
Arr(1) = ComboBox2.SelectedIndex
...
Arr(5) = NumericUpDown1.Value
Arr(6) = NumericUpDown2.Value
...
etc.
This wouldnt be too bad, except I have a LOT of these items, and writing a line for each one seems silly. I'm new to VB, so this might be an obvious solution to some. Any ideas?
Having them bound to an array would be really handy because I also allow the user to Load a CSV file, which I would like to automatically populate the ComboBoxes and NumericUpDowns from the CSV values. The only way I know to do this is to manually move each array item to the respective combobox/numeric item when they click the Load file button:
ComboBox1.SelectedIndex = Arr(0)
ComboBox2.SelectedIndex = Arr(1)
...
NumericUpDown1.Value = Arr(5)
NumericUpDown2.Value = Arr(6)
...
etc.
Edit: Here is some application info as requested...
The CSV file that can be saved/loaded looks like this:
#"Device Info","123456","asdfgh","0000","1.0x","1"
000F,0000,0032,0000,00C8,0001,0078,0101,0000,0001,0000,0001
010F,0078,0000,0103,0001,0000,000A,0005,0007,0006,0000
0001,000A,000A,000A,000A,0005,0005,0005,0002
...etc
The header line just has serial number, version, and other misc info; it is automatically generated by the target device. All of the other lines are configuration setpoints that the target device reads in and automatically configures itself. I'm writing this PC program to be able to edit (and create from scratch) these configuration CSV files with a nice GUI interface. Each item is tied to a specific setpoint, such as 000F = Language, 0032 = System Frequency, 00C8 = System Voltage, etc. The easiest way I saw to make this configuration program was to use numeric entry and drop-down comboboxes that the user can select what they want. Each NUD and CBOX equates to one of the CSV file data fields.
You can use Controls.Find() to get a reference to the desired control based on an index value. Here's a quick example to demonstrate what I mean:
For i As Integer = 1 To 30
Dim matches() As Control = Me.Controls.Find("NumericUpDown" & i, True)
If matches.Length > 0 AndAlso TypeOf matches(0) Is NumericUpDown Then
DirectCast(matches(0), NumericUpDown).Value = i
End If
Next
You can incorporate code like that into the load/save routines.
I would use binary serialization. This eliminates the need to format strings or xml when saving control properties. Similar to Plutonix's solution, it only works on certain types of control. However, it can be modified to work on any type of control - but only supports a single property to be loaded for each control. It will work on all controls of type X instead of controls named xName. You can add further restrictions by grouping the controls to be serialized in a panel or other means.
Make a new Form called Form1. Add some NumericUpDowns, TextBoxes, and ComboBoxes. Put some values in the ComboBoxes at design time, otherwise calling loadState() in Form_Load will be meaningless. However, loadState() can be called whenever (i.e. after the comboboxes have been populated).
You will need to import these two namespaces:
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
In your class:
Private Shared stateFileName As String = "SavedState.bin"
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
saveState(Me)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
loadState(Me)
End Sub
And these are the methods you will use to save and load states. The save state method:
Private Shared Sub saveState(instance As Form)
Dim controlProperties As Dictionary(Of String, Object) =
instance.Controls.OfType(Of Control).ToDictionary(Of String, Object)(
Function(c) c.Name,
Function(c)
' You can support different types of controls here too
If TypeOf c Is NumericUpDown Then
Return CType(c, NumericUpDown).Value
ElseIf TypeOf c Is ComboBox Then
Return CType(c, ComboBox).SelectedIndex
Else
' All other controls get their text property saved
' .Text is a property of Control
Return c.Text
End If
End Function)
Using myFileStream As Stream = File.Create(stateFileName)
Dim serializer As New BinaryFormatter
serializer.Serialize(myFileStream, controlProperties)
End Using
End Sub
The load state method:
Private Shared Sub loadState(instance As Form)
If File.Exists(stateFileName) Then
Using myFileStream As Stream = File.OpenRead(stateFileName)
Dim deserializer As New BinaryFormatter()
Dim controlProperties = CType(deserializer.Deserialize(myFileStream), Dictionary(Of String, Object))
For Each c As Control In instance.Controls
If controlProperties.ContainsKey(c.Name) Then
' You can support different types of controls here too
If TypeOf c Is NumericUpDown Then
CType(c, NumericUpDown).Value = CDec(controlProperties(c.Name))
ElseIf TypeOf c Is ComboBox Then
CType(c, ComboBox).SelectedIndex = CInt(controlProperties(c.Name))
Else
c.Text = controlProperties(c.Name).ToString()
End If
End If
Next
End Using
End If
End Sub
You should add exception handling, and note that a binary file is not meant to be edited by a human without the assistance of a machine.

Updating progress bar WPF Visual studio

So my issue may sound familiar, I simply want to update a progress bar concurrently with the data that is being processed in my program.
I have two windows: StartupWindow and UpdateWindow.
The Application begins by creating the startup window, the user will push a button to open the UpdateWindow. The following is the code for the button:
Private Sub btnUpdateClick(sender As Object, e As RoutedEventArgs) Handles btnUpdate.Click
Dim updateWindow As New UpdateWindow
updateWindow.ShowDialog()
btnUpdate.IsEnabled = False
End Sub
With this code I got the error most other people do: "The calling thread cannot access this object because a different thread owns it."
Private Sub updateDevice()
Dim currPercent As Integer
currPercent = ((sentPackets / totalPacketsToWrite) * 100)
progressLabel.content = currPercent.ToString + "%"
pb.Maximum = totalPacketsToWrite
pb.Value = sentPackets
If sentPackets < totalPacketsToWrite Then
'....update....
Else
MsgBox("Device now has update stored on flash!")
'...close everything up
End If
End Sub
Here are the three things I have tried so far:
Use Invoke/Dispatcher/Delegate to try and seem to only be able to put the update in a different thread's queue? Can't seem to pause other the other threads to update the UI either...
Implement the BackgroundWorker Class and use report progress from it, this worked but I could only update the progress bar under bw.DoWork I make a lot of calls and have responses from external devices so it would be difficult to put all my code under one function.
I read somewhere that since this was the second window created (called from the original) I would have issues with it. So I took someone's solution and tried to create and entire new thread when the 'update' button was pressed ie.:
Something to note is that I added a 'cancel' button:
Private Sub buttonStart_Click(sender As Object, e As RoutedEventArgs) Handles btnStart.Click
btnStart.IsEnabled = False
btnStart.Visibility = Windows.Visibility.Hidden
btnCancel.IsEnabled = True
btnCancel.Visibility = Windows.Visibility.Visible
beginUpdate()
End Sub
Private Sub buttonCancel_Click(sender As Object, e As RoutedEventArgs) Handles btnCancel.Click
btnStart.IsEnabled = True
btnStart.Visibility = Windows.Visibility.Visible
btnCancel.IsEnabled = False
btnCancel.Visibility = Windows.Visibility.Hidden
'TODO MAKE IT CANCEL
End Sub
And every time I clicked the update/cancel button the progress bar would update. Every time I pressed the button it refreshed the progress bar to the current completion. I'm quite puzzled, I am able to update user interfaces if it is just one window... but if the user pushes a button to call a new window I cannot update anything in the second window. Any suggestions?
EDIT:
I ended up making global variables that updated in my long code. Than ran backgroundworker before I did anything else and it ran asynchronous to my process, updating the progress bar:
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
While finished = False
Threading.Thread.Sleep(50)
bw.ReportProgress(currPercent)
End While
End Sub
The start of my code was simple - like so:
Private Sub buttonStart_Click(sender As Object, e As RoutedEventArgs) Handles btnStart.Click
bw.RunWorkerAsync()
beginUpdate()
End Sub
First things first... get the ProgressBar working: For this part, please read my answer to the Progress Bar update from Background worker stalling question here on Stack Overflow. Now, let's assume that you've got the basic update of a ProgressBar working... next, you want to be able to cancel the work. It is a simple matter to update the DowWork method accordingly:
private void DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
if (IsCancelled) break; // cancellation check
Thread.Sleep(100); // long running process
backgroundWorker.ReportProgress(i);
}
}
So all you need to do to cancel the long running process and the ProgressBar update is to set the IsCancelled property to true.

Infragistics UltraWinGrid EmptyDataText Equivalent?

We're using Infragistics UltraWinGrid as a base class for customized controls. One of the projects that will use this control to display search results has a requirement to display a user friendly message when no matches are located.
We'd like to encapsulate that functionality into the derived control - so no customization beyond setting the message to display is required by the programmer who uses the control. This would have to be done in generic fashion - one size fits all datasets.
Is there allowance in the UltraWinGrid for this type of usage already? If so, where would I find it hidden. :-)
If this functionality needs to be coded, I can think of an algorithm which would add a blank record to whatever recordset was set and place that into the grid. In your opinion, is this the best way to handle the solution?
I don't know if this will help, but here's to finishing up the thread. I didn't find a built in way, so I solved this problem as follows: In my class which inherits UltraGrid
Public Class MyGridPlain
Inherits Infragistics.Win.UltraWinGrid.UltraGrid
I added two properties, one to specify what the developer wants to say in the empty data case, and another to enable the developer to place their message where they want it
Private mEmptyDataText As String = String.Empty
Private mEmptyDataTextLocation As Point = New Point(30, 30)Public Shadows Property EmptyDataTextLocation() As Point
Get
Return mEmptyDataTextLocation
End Get
Set(ByVal value As Point)
mEmptyDataTextLocation = value
setEmptyMessageIfRequired()
End Set
End Property
Public Shadows Property EmptyDataText() As String
Get
Return mEmptyDataText
End Get
Set(ByVal value As String)
mEmptyDataText = value
setEmptyMessageIfRequired()
End Set
End Property
I added a method which will check for empty data and set the message if so. And another method which will remove the existing empty message.
Private Sub setEmptyMessageIfRequired()
removeExistingEmptyData()
'if there are no rows, and if there is an EmptyDataText message, display it now.
If EmptyDataText.Length > 0 AndAlso Rows.Count = 0 Then
Dim lbl As Label = New Label(EmptyDataText)
lbl.Name = "EmptyDataLabel"
lbl.Size = New Size(Width, 25)
lbl.Location = EmptyDataTextLocation
ControlUIElement.Control.Controls.Add(lbl)
End If
End SubPrivate Sub removeExistingEmptyData()
'any previous empty data messages?
Dim lblempty() As Control = Controls.Find("EmptyDataLabel", True)
If lblempty.Length > 0 Then
Controls.Remove(lblempty(0))
End If
End Sub
Last - I added a check for empty data to the grid's InitializeLayout event.
Private Sub grid_InitializeLayout(ByVal sender As Object, _
ByVal e As Infragistics.Win.UltraWinGrid.InitializeLayoutEventArgs) _
Handles MyBase.InitializeLayout
setEmptyMessageIfRequired()
End Sub

Resources