moving images in visual studio c# - c#

My program consists of a grid with 8 images (one free space). At the moment when you click on one image and then another they swap, but I only want the image to move one space at a time and only if there is a clear box. (like those scramble games)
I have this swap method, but how can I change it to fit what I want?
string click1Name="";
string click1Loc="";
string click2Name="";
string click2Loc="";
private void swap()
{
var objName=(Image)this.FindName(click1Loc);
objName.Source = new BitmapImage(new Uri("pack://application:,,,/Pic1/"+click2Name));
objName = (Image)this.FindName(click2Loc);
objName.Source = new BitmapImage(new Uri("pack://application:,,,/Pic1/" + click1Name));
}
GridPic9 is the free space (Pic9.jpg)

Not sure of what your problem is. I'm guessing when you click over one Image, you load its name and location into the corresponding global variables (click(x)Name and click(x)Loc); and then, when you click over the second Image, you load again name and location, and call Swap afterwards. Is that it?
In order to manage the swapping constrains that you stated, I would create a matrix of objects. Each object would contain info about what is shown in each Image control. The following is a naive way to do it, but it can give you an idea.
class ImageInfo
{
public string Name;
Image AssociatedControl;
public ImageInfo(string name, string loc)
{
Name=name;
AssociatedControl=(Image)this.FindName(loc);
DisplayToControl();
}
void DisplayToControl()
{
if(Name!=null)
AssociatedControl.Source = new BitmapImage(new Uri("pack://application:,,,/Pic1/"+Name));
else
AssociatedControl.Source =null;
}
public void SetImage(string name)
{
Name=name;
DisplayToControl()
}
}
ImageInfo[][] myImagesLayout = new ImageInfo[3][3];
Tuple<int,int> PreviousClickedImageCoords = null;
void myWindow_Load()
{
//Initialize the 8 images in their initial positions
myImagesLayout [0][0] = new myImagesLayout(name1,location1);
//[...]
myImagesLayout [2][1] = new myImagesLayout(name8,location8);
myImagesLayout [2][2] = new myImagesLayout(null,location9);
}
void Image1_Clicked(object sender, RoutedEventArgs e)
{
Clicked(0,0);
}
//[...]
void Image9_Clicked(object sender, RoutedEventArgs e)
{
Clicked(2,2);
}
void Clicked(int row, int column)
{
if(PreviousClickedImageCoords == null )
{
if(myImagesLayout[row][column].Name!=null)
PreviousClickedImageCoords = new Tuple<int,int>(row,column);
}
else
{
if(myImagesLayout[row][column].Name!=null)
return; //only to empty images
int prevRow=PreviousClickedImageCoords.Item1;
int prevCol=PreviousClickedImageCoords.Item2;
int step = Math.Abs(Row-prevRow) + Math.Abs(Col-prevCol);
if(step!=1)
return; //only adjacent cells. No diagonals
ImageInfo PreviousClickedImage = myImagesLayout[prevRow][prevCol];
myImagesLayout[row][column].SetImage(PreviousClickedImage.Name);
PreviousClickedImage.SetImage(null);
PreviousClickedImage=null;
}
}

Related

How do I store text file into an array and then display line by line on demand (through key binding)

I'm a teacher with a very limited programming background struggling with C# to make some videos for my students who are pretty much anxious and depressed during the pandemic. I'm using GTA5 as my platform because it is easy to customize, has amazing locations, hundreds of potential characters, and supports voice as well (I'm using Amazon Polly). Needlessly to say I'm stripping out all the violence and swearing.
The roadblock I'm hitting, and there isn't any support to speak of on any GTA forums or Mods forums, is how to display lines of text on the screen on demand. I've managed to do this hardcoded, but I would prefer to read this from a text file and display line by line, not by a timer, but on demand with a single key binding, ideally with the ability to go back and forth (but line by line).
A friend of mine who works for a AAA gaming company, doesn't know the GTA5 environment but said the solution would be to read the lines into an array. Unfortunately I don't have that programming knowledge, so I'm stuck at this point. Here is my code. Right now it will only display the last line of a test text file. Code is put together from Microsoft documentation and random GTA forum posts. Again, I can do this manually through multiple hardcoded lines of text, each with a key bind, but that's totally impractical. Need text file, one keybinding and a way go line by line (and ideally backwards)
using System;
using System.Drawing;
using System.Windows.Forms;
using GTA;
using GTA.Math;
using GTA.Native;
namespace TextDrawing
{
public class TextDraw : Script
{
public static string TextString { get; set; }
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
}
private void onTick(object sender, EventArgs e)
{
if (Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// TextString = "Default Start Screen goes here if you want it on load";
var Text4Screen = new GTA.UI.TextElement(TextString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000); //last parameter is wrap width
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.NumPad7) //code replace code in Basics_Tick
{
string[] lines = System.IO.File.ReadAllLines
(#"C:\Users\UserName\test.txt");
foreach (string line in lines)
{
TextString = line; //"Lesson 1 Topics Today. \n\n Part 1. Drawing Text on Screen \n\n Part 2. Customizations" + TextString2;
DrawText();
}
}
}
}
}
There are many ways to do this. But a few key code changes should make it work.
Create two class-level variables, an array to keep all the read files and an int index to keep track of your current index.
Create a function to read and invoke that in the constructor (ideally it should not be read in the constructor but rather triggered by external user action. But let's keep it simple for now).
In the key press event, update the string and increase the index. Your code is currently not working because you are iterating through the entire array and assigning text to a single variable. So only the last value is remains on screen.
public class TextDraw : Script
{
public static string TextString { get; set; }
public string[] AllLines { get; set; }
public int currentIndex = 0;
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
ReadAllLines(); ///Ideally not here. But should work still.
}
private void ReadAllLines()
{
AllLines = System.IO.File.ReadAllLines (#"C:\Users\UserName\test.txt");
}
private void onTick(object sender, EventArgs e)
{
if (Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// TextString = "Default Start Screen goes here if you want it on load";
var Text4Screen = new GTA.UI.TextElement(TextString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000); //last parameter is wrap width
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.NumPad7) //code replace code in Basics_Tick
{
if (currentIndex >= 0 && currentIndex < AllLines.Length)
{
TextString = AllLines[currentIndex];
}
else
{
TextString = "The End";
}
currentIndex++;
DrawText();
}
}
}
I'm not familiar with GTA scripting, but maybe create a member variable: int index = 0; Then use that to figure out which line to print in DrawText():
namespace TextDrawing
{
public class TextDraw : Script
{
// The lines of the text file
string [] lines;
// The index of the next line to print
int index = 0;
public TextDraw()
{
Tick += onTick;
Interval = 0;
KeyDown += Basics_KeyDown;
// Read the file here
lines = System.IO.File.ReadAllLines(#"C:\Users\UserName\test.txt");
}
private void onTick(object sender, EventArgs e)
{
if (0 == lines.Length || Game.IsLoading) return;
DrawText();
}
private void DrawText()
{
var pos = new Point(100, 100);
// Get next line
string textString = lines[index];
var Text4Screen = new GTA.UI.TextElement(textString, pos, 0.35f, Color.White, GTA.UI.Font.ChaletLondon, GTA.UI.Alignment.Left, true, false, 1000);
Text4Screen.Enabled = true;
Text4Screen.Draw();
}
private void Basics_KeyDown(object sender, KeyEventArgs e)
{
// When Keys.NumPad7 is pressed, show next line
if (e.KeyCode == Keys.NumPad7)
{
if (index < lines.Length - 1) index++;
}
// When Keys.NumPad8 is pressed, show previous line
else if (e.KeyCode == Keys.NumPad8)
{
if (index > 0) index--;
}
}
}
}
Updated to use onTick and go backwards. Apparently the onTick is needed to continually redraw the text. Being unfamiliar with the GTA API, I did not realize that.

Connecting a picture box being pressed to a certain letter

I have questions in my program that are jumbled up words and the user tries to un-jumble them. I want them to do this by them clicking multiple picture which have certain letters on it to form the word but I don't know how to connect the picture I have to a certain letter (e.g. Say I have a picture of the letter "a" how do I connect it so that the program knows when that picture is pressed the letter a is pressed).
This is the coding I have so far (to randomize the question and link the answers to it).
*Also I'm very new to coding so any suggestions please can you show what exactly to add/change to my code.
public partial class Level1 : Form
{
Random rnd = new Random(); //sets the random variable
List<string> strStrings = new List<string>() { "ZUZB", "HXAO", "MXAE", "KYCU", "CWEH", "PHIC", "HOCP", "SXIA", "ISHF", "KOJE" };//displays wprds om a list
Dictionary<string, string> dictStrings = new Dictionary<string, string>() //dictionary containing the word as the key, and the answer as the value in the key/value pair.
{
{ "ZUZB", "BUZZ" },
{ "HXAO", "HOAX" },
{ "MXAE", "EXAM" },
{ "KYCU", "YUCK" },
{ "CWEH", "CHEW" },
{ "PHIC", "CHIP" },
{ "HOCP", "CHOP" },
{ "SXIA", "AXIS" },
{ "ISHF", "FISH" },
{"KOJE", "JOKE" }
};
public Level1()
{
InitializeComponent();
}
int skip; //declares the skip variable
int score; //declares the score variable
int question; //decalres the question variable
private void nextButton_Click(object sender, EventArgs e)
{
if (strStrings.Count > 0)
{
string rndWord = strStrings[rnd.Next(0, strStrings.Count())];
lbljumble.Text = rndWord;
strStrings.Remove(rndWord);
}
else
{
lbljumble.Text = "No more questions!";
}
answerLabel.Text = ""; //randomly displays questions in the label until there are no more questions left to ask
score += 10; //add 10 to score and display in label
lblscore.Text = Convert.ToString(score);
question += 1; //add one to question number and display in label
lblqnum.Text = Convert.ToString(question);
tmrtime.Interval = (tmrtime.Interval) - 100; //amount of time taken after each question
}
private void answerButton_Click(object sender, EventArgs e)
{
string answer = (dictStrings.TryGetValue(lbljumble.Text, out answer)) ? answer : "";
answerLabel.Text = answer; //displays answer in label after the answer button is pressed to display the corresponding answer to the question
}
private void btnskip_Click(object sender, EventArgs e)
{
skip = 1; //skip equals 1
if (skip == 1) //of skip equals one
{
skip--; //take one from skip
lblskip.Text = " remaining: no"; //display that no skips are available
}
else
{
lblskip.Text = "No skips remaining"; //display no skips remaining
}
}
}
}
If I understand your question correctly you would like to know what letter was selected/clicked by the user.
You can dynamically create picture boxes, assign the .Name of the picture box to the value you would like the picture box to have then subscribe to the Click event for each picture box.
In the click event check the name of the sending picture box object sender. (you could alternatively use the pictureBx.Tag if you don't want to use the pictureBx.Name )
Here is an example form.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
lblAnswer.Text = "";
DrawLeters();
}
string checkAnswer = "check";
void DrawLeters()
{
this.SuspendLayout();
string[] alphabet = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z".Split(",");
var pictureLocation = new Point(0, 0);
for (int i = 0; i < alphabet.Length; i++)
{
//Create a picture box
PictureBox pictureBx = new PictureBox();
//set the default location of the box (0,0) in this scenario
pictureBx.Location = pictureLocation;
//make the box 16 by 16
pictureBx.Size = new Size(16, 16);
//set the name of the box to that of the letter it represents
pictureBx.Name = alphabet[i];
//assign a click event to the box
pictureBx.Click += PictureBx_Click;
//now create the image that will fill the box
Image img = new Bitmap(16, 16);
using (Graphics graph = Graphics.FromImage(img))
{
graph.Clear(Color.White);
Brush textBrush = new SolidBrush(Color.Black);
graph.DrawString(pictureBx.Name, this.Font, textBrush, 0, 0);
graph.Save();
}
//assign the image to the box
pictureBx.Image = img;
//add the box to the form
this.Controls.Add(pictureBx);
//change the location for the next box
pictureLocation.X += 17;
if (i % 10 == 0 && i > 0)
{
pictureLocation.Y += 17;
pictureLocation.X = 0;
}
}
this.ResumeLayout(false);
this.PerformLayout();
}
private void PictureBx_Click(object sender, EventArgs e)
{ // assign the clicked value to the answer label
if (sender is PictureBox pbx)
lblAnswer.Text += pbx.Name;
}
private void checkAnswerBtn_Click(object sender, EventArgs e)
{
//check the answer
if (lblAnswer.Text == checkAnswer)
lblFeedback.Text = "Correct!!";
else
lblFeedback.Text = "NO!!";
}
private void clearBtn_Click(object sender, EventArgs e)
{
//clear the answer label
lblAnswer.Text = "";
}
}
and the result is this.
i'm just checking that the answer is check you would use your own logic to determine the correct answer.
You can add some picture-boxes and name them as Letter_a_pb, Letter_b_pb etc., create a Click event, and then create an if statement to check which pictureBox is clicked and if it equal to the letter 'a'.
For instance:
string question = "a";
private void Letter_a_pb_Click(object sender, EventArgs e)
{
if (question == "a")
MessageBox.Show("Excellent!");
else
MessageBox.Show("Try Again!");
}
Let me know if this is what you've searched for.

C# - ListView won't refresh from different form

// PaddlerList (Form 1)
public void RefreshListView()
{
PaddlerListView.Items.Clear();
PaddlerToList();
PaddlerListView.Refresh();
Console.WriteLine("Refresh....");
}
public void PaddlerToList() // Just adds
{
for (int i = 1; i < Main.paddlerList.Count(); i++) // Repeats for all
{
string[] array = new string[4];
ListViewItem item;
array[0] = Main.paddlerList[i].FirstName.ToString();
array[1] = Main.paddlerList[i].LastName.ToString();
array[2] = Main.paddlerList[i].Weight.ToString();
array[3] = Main.paddlerList[i].PreferredSide.ToString();
item = new ListViewItem(array);
PaddlerListView.Items.Add(item);
}
}
private void RefreshList_button_Click(object sender, EventArgs e)
{
RefreshListView();
}
// NewPaddler (Form 2)
private void SubmitPaddler_button_Click(object sender, EventArgs e)
{
// Code here
PaddlerList ListViewRefresh = new PaddlerList(); // Creates an instant of the other form, so it can run the procedure
ListViewRefresh.RefreshListView();
}
When a new paddler is added (the submit button is pressed), the console outputs "Refresh...." showing that the function has been run, but the ListView doesn't refresh
However, when I set a button on the same form as the ListView, it does refresh the ListView with the new items, when the button is pressed.
I can't work out what the issue is here? I think it's something to do with protection levels of the ListView.
Thanks!
When a new PaddlerList object is created, all member variables, like in your case, Items, recieve a fresh start. I think you need to pass the original PaddlerList to your second form in order to actualize the correct form.
I found a solution:
New Paddler Form
// NewPaddler Form
PaddlerList ListObject_global = null;
public NewPaddler(PaddlerList PaddlerList_Object)
{
InitializeComponent();
FormBorderStyle = FormBorderStyle.FixedDialog;
ListObject_global = PaddlerList_Object;
}
if (ListObject_global != null)
{
ListObject_global.RefreshListView(); // Runs function in PaddlerList
}
Paddler List Form
// PaddlerList Form
public void RefreshListView()
{
PaddlerToList();
PaddlerListView.Refresh();
Console.WriteLine("Refresh....");
}

C# saving colours of many things

I have managed to get a colour-picker on the go which works, but I want to be able to save the colours the user wants. I have made links to the colordialog for labels/comboboxes/other controls as seperate items which works, but I would like to know how to save them (maybe to a txt file or something) as when the program restarts, it reverts back to black and white.
Thing is, it's not just 1 or 2 colour changes, I have got 35 labels, 7 comboboxes, 4 radiobuttons, 5 tabpages, 22 textboxes (text & background of each), 10 buttons (text and background of each) and A LOT of numericupdowns (text and background of each) - I have just spent the last hour sorting each one with the colour-picker...I don't want to have to do something to each thing in turn if I can help it...is there a way, or am I going to have to go through them all :)
I would follow the process of saving the colors on form closing, loading the colors on the form load, then applying the default colors after they have loaded.
I store the settings into a global variable inside of the form so I can reference them by name.
Settings Variable:
public Dictionary<string, FieldColor> colorSettings = new Dictionary<string, FieldColor>();
I use this following structure because you currently have two colors, but you could add more properties should the need arise.
Data Structure:
public struct FieldColor
{
public Color BackColor;
public Color ForeColor;
public FieldColor(Color backColor, Color foreColor)
{
this.BackColor = backColor;
this.ForeColor = foreColor;
}
}
The save method iterates through each control, then iterates through it's children controls, and if the color isn't the form's default color it saves the line. This saves it as a csv.
Save:
public void SaveFieldColors(Control parent, StreamWriter colorWriter)
{
foreach (Control currentControl in parent.Controls)
{
if (currentControl.ForeColor != DefaultForeColor || currentControl.BackColor != DefaultBackColor)
{
//Save the file in your format
colorWriter.WriteLine(currentControl.Name + "," + currentControl.BackColor.ToArgb() + "," + currentControl.ForeColor.ToArgb());
}
ApplyDefaultColorToControls(currentControl);
}
}
To load the settings I just break each line into a entry into the colorSettings dictionary.
Load:
public void LoadFieldColors(StreamReader colorReader)
{
while (colorReader.EndOfStream == false)
{
//Read the file from your format
string line = colorReader.ReadLine();
string[] fieldData = line.Split(',');
colorSettings.Add(fieldData[0], new FieldColor(Color.FromArgb(int.Parse(fieldData[1])), Color.FromArgb(int.Parse(fieldData[2]))));
}
}
Here I iterate through each control and their children to apply the formatting if there's an entry for that control's name. If there isn't one I just set it to the default.
Apply the Formatting:
public void ApplyDefaultColorToControls(Control parent)
{
foreach (Control currentControl in parent.Controls)
{
if (colorSettings.ContainsKey(currentControl.Name) == true)
{
//Set the controls settings equal to their color in your stored dictionary
currentControl.BackColor = colorSettings[currentControl.Name].BackColor;
currentControl.ForeColor = colorSettings[currentControl.Name].ForeColor;
}
else
{
//Set the control settings equal to default settings
currentControl.BackColor = DefaultBackColor;
currentControl.ForeColor = DefaultForeColor;
}
ApplyDefaultColorToControls(currentControl);
}
}
Example Calls from the form events:
private void Form1_Load(object sender, EventArgs e)
{
using (StreamReader settingsReader = new StreamReader(#"C:\path\to\file\filename.ending"))
{
LoadFieldColors(settingsReader);
}
ApplyDefaultColorToControls(this);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
using (StreamWriter settingsWriter = new StreamWriter(#"C:\path\to\file\filename.ending"))
{
SaveFieldColors(this, settingsWriter);
}
}
}
There are many ways to store data in c#, starting with a simple text file and ending in databases... this is too broad. However you choose to save the data, I suggest to start with a Dictonary<string, Touple<color, color>> where the string is the control name, one is the backColor and the other is the foreColor and serialize it into a file (xml or json, doesn't matter all that much).
When you load your form, use a recursive function on
this.Controls to change the colors for each control.
You can try this if it is for WinForms project.
Create class for your settings :
[Serializable]
sealed class Settings
{
internal Dictionary<string, Color> ForeColors { get; set; }
internal Dictionary<string, Color> BackColors { get; set; }
internal Settings()
{
ForeColors = new Dictionary<string, Color>();
BackColors = new Dictionary<string, Color>();
}
internal void Save(string fileName = #"settings.ini")
{
using (FileStream stream = File.Create(Directory.GetCurrentDirectory() + "\\" + fileName))
{
BinaryFormatter serializer = new BinaryFormatter();
serializer.Serialize(stream, this);
}
}
internal static Settings Load(string fileName = #"settings.ini")
{
if (!File.Exists(Directory.GetCurrentDirectory() + "\\" + fileName)) return null;
using (FileStream stream = File.OpenRead(Directory.GetCurrentDirectory() + "\\" + fileName))
{
BinaryFormatter serializer = new BinaryFormatter();
return serializer.Deserialize(stream) as Settings;
}
}
}
Now in your windows form subscribe to Form_Load Form_Closing events and add handlers:
private void Form1_Load(object sender, EventArgs e)
{
Settings s = Settings.Load();
if (s == null) return;
foreach (Control item in this.Controls)
{
item.ForeColor = s.ForeColors[item.Name];
item.BackColor = s.BackColors[item.Name];
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Settings s = new Settings();
foreach (Control item in this.Controls)
{
s.ForeColors.Add(item.Name, item.ForeColor);
s.BackColors.Add(item.Name, item.BackColor);
}
s.Save();
}

Get a random card and change the label

I'm just fooling around in Windows Forms and wish to click a button and then give a player 2 random cards, but when I click the button the label is empty. How do i pass the value to the string?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void DealTheCardsButton_Click(object sender, EventArgs e)
{
TheCards theCards = new TheCards();
CardOneLabel.Text = theCards.card1;
}
}
public class TheCards
{
public TheCards()
{
Cards = new List<string>();
Cards.Add("1");
Cards.Add("2");
Cards.Add("3");
Cards.Add("4");
}
public List<string> Cards { get; set; }
public string card1;
public string card2;
public string cardTest = "hej";
public void GiveTwoRandomCardsFromCardsList()
{
Random random = new Random();
int slumptal = random.Next(0, 4);
card1 = Cards[1];
bool flag = false;
while (!flag)
{
Random random2 = new Random();
int slumptal2 = random2.Next(0, 4);
if (slumptal != slumptal2)
{
card2 = Cards[slumptal2];
flag = true;
}
}
}
}
If I call to change the CardOneLabel.Text to a string with a hardcoded value in it it works. It seems like the randomizing doesn't change the value of string card1 and string card2. As it is now, when I click on the button the label changes its value to nothing (empty).
How can i change the label value to a random card value?
You never call the GiveTwoRandomCardsFromCardsList method. Try this:
private void DealTheCardsButton_Click(object sender, EventArgs e)
{
TheCards theCards = new TheCards();
theCards.GiveTwoRandomCardsFromCardsList();
CardOneLabel.Text = theCards.card1;
}
By the way, don't create a new Random object for every random number you require. This might even give you the same random number due to how the pseudo-random number generator depends on the current time. Just use the same Random instance over and over again. It will give you a new random number every time.
Also, you will sometimes get the same card twice. I'll leave it to the OP to figure out how to always get two different cards, if that's the idea.
But when I click the button the label is empty. How do i pass the
value to the string?
When you try to assign the value
CardOneLabel.Text = theCards.card1;
The constructor of that class TheCards is not initiating the card1 variable to anything, you are retrieving an empty string.
The constructor below, is adding to the list Cards but nothing else is taking place.. Do you have additional code your not showing?
public TheCards()
{
Cards = new List<string>();
Cards.Add("1");
Cards.Add("2");
Cards.Add("3");
Cards.Add("4");
}
Your CardOneLabel is empty when you press the button because you are literally reading an empty string. Try calling your GiveTwoRandomCardsFromCardsList() method before assigning.

Categories