Random browsing of images (animated) - vb.net

I was trying to make a program that shows picture in a folder.
The path of each picture was stored in the database.
My problem was it only show the last images stored in the database and not all the pictures.
The code is:
Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Timer1.Enabled = True
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Try
Dim ctr As Integer = 0
Dim pic As String
Dim sqlconn As New SqlConnection("data source=NMPI_2;initial catalog=IPCS; " & _
"password=rhyatco; " & _
"persist security info=True; " & _
"user id= rhyatco;" & _
"packet size=4096")
sqlconn.Open()
Dim query As String = "Select picture from Bpicture"
Dim sqlcomm As New SqlCommand(query, sqlconn)
Dim reader As SqlDataReader
reader = sqlcomm.ExecuteReader
While reader.Read
pic = reader("picture").ToString
Me.PictureBox1.Image = Image.FromFile(pic)
Me.PictureBox1.SizeMode = PictureBoxSizeMode.StretchImage
End While
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub

It's always going to show the last image, because you are retrieving the entire table and retrieving all the records with the While loop. The last image will always be the winner.
There are two possible solutions:
One, randomly retrieve one image from the database. Check this stackoverflow thread on how to randomly select a row. The caveat with this solution is you make a call to the database every time.
Two, retrieve all the images from the database, store the data in a collection and randomly select one of the images from the collection. The caveat with this solution is there maybe too many images to store in a collection in memory, in which case you have to go with One.

Well the problem that I see with what you've provided is that you're running the query and iterating through all the rows in the result set every time the timer fires.
I think what you're really looking for is to download the result set once when the form loads, and then to switch which picture you're looking at on the timer.
One way to just rotate through them all over and over again would be to download the list and populate them into a Queue of filenames, and then on the timer just do something like this (sorry, my example is in C#):
string fileName = imageFileNameQueue.Dequeue();
PictureBox1.Image = Image.FromFile(fileName);
imageFileNameQueue.Enqueue (fileName); // Put the file back at the back of the queue

A simple change to your existing solution that will kind of work is to break out of the while loop with some probability. A problem with this solution is that images later in the query result are much less likely to be shown than earlier images.
I haven't done any VB so I'm coding via Google, but you'll need to create an instance of Random somewhere:
Dim rand as new Random()
Then in your while loop, pull off a random number to see if you should stop:
While reader.Read
...
If rand.Next(10) > 8 Then
Exit While
End If
End While
EDIT: You should also move the code to set the Image and SizeMode out of the while loop so that they're only set one time, once you've decided on the image.

Related

How to only accept certain values inside a textbox

I made a flashcard application where the user can change the difficulty by entering a number inside a textbox.
Sub UpdateDifficultyLevel(front As String, difficulty As Integer)
'The parameters are represented by question marks in the query
Dim sql = "UPDATE flashcards SET difficulty = ?
WHERE Front = ?"
'This using statement The Using statement makes sure that any "unmanaged resources" are released after they've been used.
Using conn As New OleDbConnection("provider=microsoft.ACE.OLEDB.12.0;Data Source=flashcard login.accdb"), 'Establish connection
cmd As New OleDbCommand(sql, conn)
cmd.Parameters.Add("#difficulty", OleDbType.Integer).Value = difficulty 'Updates database with parameters
cmd.Parameters.Add("#front", OleDbType.VarWChar).Value = front 'Updates database with parameters
conn.Open() 'Opens connection
cmd.ExecuteNonQuery() 'Executes
End Using
End Sub
Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
Dim difficulty As Integer 'Sets difficulty as integer
If Integer.TryParse(TxtDifficulty.Text, difficulty) Then
Dim front = txtFront.Text 'Defines front as variable which is equal to txtfront.text
UpdateDifficultyLevel(front, difficulty) 'Calls subroutine
Else
MsgBox("Please enter a number between 1 and 3") ' tells user that the difficulty must be a number
End If
End Sub
This works where the user can only enter an integer but how would I make it so they can only enter an integer between 1 and 3
You can utilize ErrorProvider to feedback to the user that the input is wrong. And reuse the validate function to get the parsed integer when used (so the logic is only in one place)
Private errorMessage As String = "Please enter a number between 1 and 3"
Private Function validateInput(ByRef difficulty As Integer) As Boolean
Return Integer.TryParse(TxtDifficulty.Text, difficulty) AndAlso difficulty >= 1 AndAlso difficulty <= 3
End Function
Private Sub TxtDifficulty_TextChanged(sender As Object, e As EventArgs) Handles TxtDifficulty.TextChanged
If Not validateInput(Nothing) Then
ErrorProvider1.SetError(TxtDifficulty, errorMessage)
TxtDifficulty.SelectAll()
Else
ErrorProvider1.SetError(TxtDifficulty, "")
End If
End Sub
Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
Dim difficulty As Integer 'Sets difficulty as integer
If validateInput(difficulty) Then
Dim front = txtFront.Text 'Defines front as variable which is equal to txtfront.text
UpdateDifficultyLevel(front, difficulty) 'Calls subroutine
Else
MsgBox(errorMessage) ' tells user that the difficulty must be a number
End If
End Sub
There are many other, possibly better, ways to do this however, but this sticks with your current design.
Use a NumericUpDown control instead, with its Increment set to 1, MinValue set to 1 and MaxValue set to 3. It looks just like a textbox and, bonus, you can change the number within using the arrow keys
You can also consider a MaskedTextBox, RadioButtons, ComboBox, ListBox, even 3 different buttons to start an Easy, Medium or Hard game..
A big part of effective UI design is in using tools designed for purpose and not bothering the user with an endless succession of error messages that they have to read, understand and apply corrections for. A great example of that gone wrong is a cellphone with a Symbian OS; they were renown for bothering the user with incessant messages.
If you have an opportunity to design a UI so the user simply can't get it wrong, rather than shouting at them when they do, take it; your iPhone doesn't present a Qwerty keyboard when you're dialling a number..

Update From DataGridView to Database

Dim sb As New MySqlConnectionStringBuilder
sb.Server = Form1.hostname.Text
sb.UserID = Form1.rootuser.Text
sb.Password = Form1.rootpassword.Text
sb.Database = Form1.hostdb.Text
sb.Port = Form1.hostport.Text
Using connection As New MySqlConnection(sb.ConnectionString)
Try
connection.Open()
Dim adapter1 As New MySqlDataAdapter(TextBox1.Text, connection)
Dim cmdb1 = New MySqlCommandBuilder(adapter1)
Dim ds As New DataSet
Dim words As String() = TextBox1.Text.Split(New Char() {" "c})
Dim tablenamewords = (words(3))
adapter1.Update(ds, tablenamewords)
Catch ex As MySqlException
MessageBox.Show(ex.Message)
Finally
connection.Dispose()
End Try
End Using
It's giving me this error:
Update unable to find TableMapping['creature_template'] or DataTable 'creature_template'.
I want to select items then use a DataGrid to update them.
Note: tablenamewords = "creature_template"
You can solve this in alot of different methods.
I prefer to update the DB with a single query every time the the user ends editing a cell.
BUT FIRST you have to bind a source to your DataGridView!
How to bind a source:
Go to your application design an click on your DataGridView
Click on the pause/start like little button as shown:
Click on the "ComboBox" like textbox labeled as "chose data source" as shown and click on it:
It will open a "form", click on the Add data source as shown:
Follow the wizard, you can do it without any furhter explanation!
Well done, you almost completed it. Just go in your code, and look for this sub:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.your_table_nameTableAdapter.Fill(Me.your_db_nameDataSet.your_table_name)
End Sub
The line inside your Form1_Load Sub will upload the data inside your DataGridView, leave it there if you want it to load with the form or cut/paste it wherever you like.
Now you can add this the code to programmatically update the DB:
Private Sub DataGridView1_CellEndEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DataGridView1.CellEndEdit
Try
Dim location As Point = DataGridView1.CurrentCellAddress
Dim sqlCmd As String = "UPDATE table_name SET " & _
DataGridView1.Columns(location.X).Name.Replace("DataGridViewTextBoxColumn", "") & " = #Data " & _
"WHERE " & _
DataGridView1.Columns(0).Name.Replace("DataGridViewTextBoxColumn", "") & " = #ID"
Using myConn As New SqlConnection(My.Settings.VISURE_DA_PDFConnectionString)
myConn.Open()
Using myCmd As New SqlCommand(sqlCmd, myConn)
myCmd.Parameters.Add("#Data", SqlDbType.VarChar)
myCmd.Parameters.Add("#ID", SqlDbType.Int)
myCmd.Parameters("#Data").Value = DataGridView1.Rows(location.Y).Cells(location.X).Value
myCmd.Parameters("#ID").Value = DataGridView1.Rows(0).Cells(location.X).Value
myCmd.ExecuteNonQuery()
End Using
myConn.Close()
End Using
Catch ex As Exception
Err.Clear()
End Try
End Sub
Remarks:
location.X is the col index, location.Y is the row index
DataGridView1.Columns(0).Name This is my ID (primary key) and it is always in the first column. Change it with yours.
Don't forget to add .Replace("DataGridViewTextBoxColumn", "") whan you are getting your column name
As previously said, this code will update your DB every time the user edits one cell on your DataGridView updating only the edited cell. This is not a big issue because if you want to edit two ore more cell, every time you leave an edited cell the method DataGridView1_CellEndEdit is fired!

DataGridView DataSource // integer to icon

I've got a list-based application that runs very slow, because each row in my DataGridView is build manually as DataGridViewRow. To fix performance issues i decided to use a DataSource (DataTable) instead.
My problem:
One column I am recieving from my database is filled with image IDs. The application knows which image belongs to which id. When creating gridrows manually there was no problem in translating int to a bitmap and use the bitmap as value for an imagecolumn. Now using the Datatable instead i cant get it working. For testing i wrote the following class:
Public Class test
Dim img As Bitmap
Public Sub New(image As Bitmap)
InitializeComponent()
Me.img = image
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim dt As New DataTable
dt.Columns.Add("StrCol")
dt.Columns.Add("ImgCol")
Dim row As DataRow = dt.NewRow
row.Item(0) = "Ohne Bild"
row.Item(1) = 0
Dim row2 As DataRow = dt.NewRow
row2.Item(0) = "Mit Bild"
row2.Item(1) = 1
dt.Rows.Add(row)
dt.Rows.Add(row2)
DataGridView1.DataSource = dt
End Sub
Private Sub DataGridView1_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles DataGridView1.CellFormatting
If e.ColumnIndex = 1 Then
If e.Value = 0 Then
e.Value = Nothing
Else
e.Value = img
End If
End If
End Sub
End Class
hint:
"Ohne Bild" translates into "Without image"
"Mit Bild" translates into "With image"
Output after Button1_Click()
EDIT
Due to a missunderstanding I will try to clarify my question;
By list-based i dont actually mean the list-object. My bad there.By list-based i mean an application that displays the user Database entrys he made. The very basic of my application is simple: Allow user to add Rows, Edit and delete rows in a datagridview. This information is saved and edited in a database. Some of these informations in the rows (2 columns to be exactly) are displayed as icons while the application simply saved an id for the icon in the database. So the application knows which ID belongs to which icon.
The sample dt in the question is just to have something to work with. In the application it will be data recieved from a database to a datatable.
I finally solved it myself.
I get a datatable from my database.
Then I add a Column to it with the DataType Bitmap
Then I loop through each row of the DataTable and translate the Image_ID column to the image itself and put the image into the new Column
Then I remove the Image_ID column
Then I use the new DataTable as DataSource
I created that code in my main application which is a lot more complex so I can't show the code here. Sorry for that and thanks for your help.
It is important to do every change you need to do before using the table as source, because as soon as the gridView has to change something that is displayed it takes a lot more time.

already an open DataReader associated with this Command when am chekcing with invalid data

Private Sub txt_sname_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles txt_sname.GotFocus
Dim fcs As String
fcs = "select fname,dept from nstudent where stid = '" & txt_sid.Text & "'"
scmd1 = New SqlCommand(fcs, con)
dr1 = scmd1.ExecuteReader
If dr1.HasRows Then
Do While (dr1.Read)
txt_sname.Text = dr1.Item(0)
cmb_dept.Text = dr1.Item(1)
Loop
Else
MsgBox("Not Found")
End If
scmd1.Dispose()
If Not dr1.IsClosed Then dr1.Close()
End Sub
The above code for data from database and pass to textbox. When am running the program and checking with data which already present in database, it working properly. but checking with some other data(which not present in db)following error is occurring and exiting.
error:
"There is already an open DataReader associated with this Command which must be closed first."
pls help me..
Some observations:
Instead of using a global command object, use a local one. Especially since you are creating a new command anyway. And it looks like this is true of the dr1 as well.
You are not preventing SQL injection, so someone can type text in txt_sid that causes security issues by deleting data, dropping tables, or getting access to other data in the database.
You are looping and setting the same variables multiple times. If there is only going to be one record, don't bother looping.
Wrap the entire thing around a try/catch
Private Sub txt_sname_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles txt_sname.GotFocus
Dim cmd1 As SqlCommand = New SqlCommand(fcs, "select fname,dept from nstudent where stid = #stid")
cmd1.Parameters.Parameters.AddWithValue("#stid", txt_sid.Text)
Dim studentReader as SqlDataReader
Try
studentReader = scmd1.ExecuteReader
If studentReader.Read Then
txt_sname.Text = studentReader.Item(0)
cmb_dept.Text = studentReader.Item(1)
Else
MsgBox("Not Found")
End If
Finally
studentReader.Close()
cmd1.Dispose()
End Try
End Sub
Finally, I think you might want to actually do this when txt_sid changes, not when txt_sname gets focus.

how do I refresh the textbox/datatable to re-run this command?

This piece of code grabs a customers hire record using their hire ID and displays their details in multiple textboxes. It all works fine and well, however, I can only run it once. If I type in another customers hire record ID it just displays the first customers details that were materialised, which I assume is because the datatable has been populated and not refreshed based on the new hire record ID I've entered.
Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
Dim hirerecord1 As Integer = TextBox13.Text
Dim cmd2 As New SqlCommand("SELECT * FROM HireItemRecord WHERE HireRecord_Id = " & hirerecord1, cnn)
Dim sqlDa As New SqlDataAdapter(cmd2)
sqlDa.Fill(dt1)
If dt1.Rows.Count > 0 Then
TextBox14.Text = dt1.Rows(0)("RentalItem_Id").ToString()
TextBox15.Text = dt1.Rows(0)("HireRecord_Id").ToString()
TextBox45.Text = dt1.Rows(0)("HireItemBeginDate").ToString()
End If
cnn.Close()
End Sub
I'm not quite sure what to do to fix this...
Also, I'm having a similar problem with this..
Private Sub TextBox46_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox46.TextChanged
Dim keywords2 As String = TextBox46.Text
ds1.Tables("PersonDetails").DefaultView.RowFilter = "Last_Name like '%" & keywords2 & "%' "
End Sub
With this I can search a column of a datagridview for a match. It works all fine and well, until I insert a new record into the database at which point I refresh the datagridview to display the newly added record. After I do this, I can no longer search using the textbox. Once again, I'm not quite sure what to do to fix this issue.
Thank you very much for your help.
You're closing your connection at the end of the Button6_Click method and I can't see where it gets opened again. That would fit with your symptoms of the method only running successfully once. Have you tried to step through the code to see where it fails the second time?