Problem solved.
The original "private void buttonSave_Click" was changed to:
private void buttonSave_Click(object sender, EventArgs e)
{
if (MusicCollection.FormMain.PublicVars.AlbumList.Count != 100)
{
MusicCollection.FormMain.PublicVars.AlbumList.Add(new Album(NameTextBox.Text));
MessageBox.Show("New Album added: " + NameTextBox.Text);
formMain.ListAlbums(formMain.AlbumsListBox.Items);
this.Close();
}
else
{
MessageBox.Show("No room for new album.");
this.Close();
}
}
Original Post:
I'm new to using C#, so appologies for any seemly obvious mistakes or terrible coding.
I'm trying to create a new Album object (that gets its Name from NameTextBox.Text on Form FormAlbumAC) and add it to List AlbumList when the user clicks the save button on FormAlbumAC. Then I want to list all of AlbumList in a ListBox on Form FormMain.
When I run the program and click the save button, I'm getting the error "ArgumentOutOfRangeException was unhandled, Index was out of range" at the line:
if (MusicCollection.FormMain.PublicVars.AlbumList[i] == null)
// line 8 on my excerpt from Form FormAblumAC
I'm not sure what I'm doing wrong. Any help would be much appreciated, thank you.
Form FormMain:
public const int MAX_ALBUMS = 100;
public int totalAlbums = 0;
public FormMain()
{
InitializeComponent();
}
public static class PublicVars
{
public static List<Album> AlbumList { get; set; }
static PublicVars()
{
AlbumList = new List<Album>(MAX_ALBUMS);
}
}
public ListBox AlbumListBox
{
get
{
return AlbumListBox;
}
}
public void ListAlbums(IList list)
{
list.Clear();
foreach (var album in PublicVars.AlbumList)
{
if (album == null)
continue;
list.Add(album.Name);
}
}
Form FormAlbumAC:
private FormMain formMain;
private void buttonSave_Click(object sender, EventArgs e)
{
int index = -1;
for (int i = 0; i < MusicCollection.FormMain.MAX_ALBUMS; ++i)
{
if (MusicCollection.FormMain.PublicVars.AlbumList[i] == null)
{
index = i;
break;
}
}
if (index != -1)
{
MusicCollection.FormMain.PublicVars.AlbumList[index] = new Album(NameTextBox.Text);
++formMain.totalAlbums;
MessageBox.Show("New Album added: " + NameTextBox.Text);
formMain.ListAlbums(formMain.AlbumsListBox.Items);
this.Close();
}
else
{
MessageBox.Show("No room for new album.");
this.Close();
}
}
Your problem (from your comments) is that your for loop's condition is incorrect. Your for loop is this:
for (int i = 0; i < MusicCollection.FormMain.MAX_ALBUMS; ++i)
There is one problem and one potential problem here. First, when this code is actually run, it's really running:
for (int i = 0; i < 100; ++i)
because MusicCollection.FormMain.MAX_ALBUMS is declared as 100. This causes an error when the length of MusicCollection.FormMain.PublicVars.AlbumList is less than 100, because you're trying to grab an index that doesn't exist.
Instead, you need to iterate from i=0 to the length of ....PublicVars.AlbumList-1, or, preferably, for(int i = 0; i < ....PublicVars.AlbumList.Count; i++).
The second potential problem is that you are potentially skipping index 0. Arrays start at index zero and continue to index length-1. As such, you probably want i++, not ++i. Depends on your implementation, though.
Related
I'm using C# Windows Forms Application to create a simple application. I've used a listBox to read and display a text file (10 lines). How to specify the First, Last, Previous and Next line in the listBox by pressing a proper button? Any help will be appreciated. Thank you.
[enter image description here][1]
How to specify the First, Last, Previous and Next line in the listBox by pressing a proper button?
Note: This answer assumes you want only show one line at a time, either it be the first line, next, previous and or last line in the text file.
Let's first go over your current approach. The routine button6_Click is only trying to load a file, read the line and if the line/string is not null then add this string to listBox1; this is doing this for every string in that file. The usage of OpenFileDialog and StreamReader implement IDisposable, you should wrap these in a using block to ensure objects are properly disposed of as well.
Your current approach is more than likely not what you need as you don't want to load everything up at once, only when needed. With this in mind, you would need to keep some sort of collection of these lines from the file; there's more than a few ways, but I will focus on just one way.
First, create a new class as per below:
public class NavigateFile
{
// Stores the file lines collection
public IEnumerable<string> CurrentFileLines { get; private set; }
// Tracks the current line index
public int CurrentFileLineIndex { get; private set; } = 0;
// Tracks the next line index
public int NextFileLineIndex { get; private set; } = 1;
// Tracks the previous line index
public int PreviousFileLineIndex { get; private set; } = -1;
// Tracks the last line index
public int LastFileLineIndex { get; private set; } = 0;
// Routine to load the file
public void LoadFile()
{
using (OpenFileDialog ofd = new OpenFileDialog())
if (ofd.ShowDialog() == DialogResult.OK)
CurrentFileLines = File.ReadLines(ofd.FileName).Where(line => !string.IsNullOrEmpty(line));
CurrentFileLineIndex = 0;
NextFileLineIndex = 1;
PreviousFileLineIndex = -1;
LastFileLineIndex = CurrentFileLines.Count();
}
// Routine return the first line in the file
public string GoToFirstLine()
{
CurrentFileLineIndex = 0;
return CurrentFileLines.ToList()[CurrentFileLineIndex];
}
// Routine return the next line in the file if we can
public string GoToNextLine()
{
if((CurrentFileLineIndex + 1) <= CurrentFileLines.Count() - 1)
{
CurrentFileLineIndex++;
return CurrentFileLines.ToList()[CurrentFileLineIndex];
}
return CurrentFileLines.ToList()[CurrentFileLineIndex];
}
// Routine return the previous line in the file if we can
public string GoToPreviousLine()
{
if ((CurrentFileLineIndex - 1) >= 0 && (CurrentFileLineIndex - 1) <= CurrentFileLines.Count())
{
CurrentFileLineIndex--;
return CurrentFileLines.ToList()[CurrentFileLineIndex];
}
return CurrentFileLines.ToList()[CurrentFileLineIndex];
}
// Routine return the last line in the file
public string GoToLastLine()
{
CurrentFileLineIndex = CurrentFileLines.Count() - 1;
return CurrentFileLines.Last();
}
}
Next you could use this simply by calling a few functions to return what you would need. Put below logic in your specific buttons to handle what you would need to.
// put this in your class where you will be using the new class
private NavigateFile NavigateFile { get; set; } = new NavigateFile();
// Load a file
listBox1.Items.Clear();
NavigateFile.LoadFile();
listBox1.Items.Add(NavigateFile.GoToFirstLine());
// Go to first line
listBox1.Items.Clear();
listBox1.Items.Add(NavigateFile.GoToFirstLine());
// Go to next line
listBox1.Items.Clear();
listBox1.Items.Add(NavigateFile.GoToNextLine());
// Go to previous line
listBox1.Items.Clear();
listBox1.Items.Add(NavigateFile.GoToPreviousLine());
// Go to last line in the file
listBox1.Items.Clear();
listBox1.Items.Add(NavigateFile.GoToLastLine());
If you should need to lookup an item, you can build a Linq query against CurrentFileLines in the NavigateFile class and pull what you would need, it may be helpful if needed somewhere.
Note:
I was clearing out the listBox1.Items every time I did something, you don't have to, but I did. Also keep in mind, make sure you have a file loaded before calling any of these functions and or edit them to make sure you have data. If there is something you don't understand, please let me know and I can help.
I ~think~ you're asking how to change the selected item in the listbox by pressing the buttons?
If so, something like:
OpenFileDialog openFile = new OpenFileDialog();
private void btnLoadFile_Click(object sender, EventArgs e)
{
if (openFile.ShowDialog() == DialogResult.OK )
{
try
{
List<String> lines = new List<String>(System.IO.File.ReadAllLines(openFile.FileName));
lines.RemoveAll(l => l.Trim().Length == 0);
if (lines.Count > 0)
{
listBox1.Items.Clear();
listBox1.Items.AddRange(lines.ToArray());
}
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString(), "Error Loading File");
}
}
}
private void btnFirst_Click(object sender, EventArgs e)
{
if (listBox1.Items.Count > 0)
{
listBox1.SelectedIndex = 0;
}
}
private void btnLast_Click(object sender, EventArgs e)
{
if (listBox1.Items.Count > 0)
{
listBox1.SelectedIndex = (listBox1.Items.Count - 1);
}
}
private void btnPrevious_Click(object sender, EventArgs e)
{
if (listBox1.Items.Count > 0)
{
listBox1.SelectedIndex = (listBox1.SelectedIndex > 0) ? (listBox1.SelectedIndex - 1) : 0;
}
}
private void btnNext_Click(object sender, EventArgs e)
{
if (listBox1.Items.Count > 0)
{
listBox1.SelectedIndex = (listBox1.SelectedIndex < (listBox1.Items.Count - 1)) ? (listBox1.SelectedIndex + 1) : (listBox1.Items.Count - 1);
}
}
Note that if no item is currently selected, then pressing previous or next will select the first item in the listbox.
I'm trying to build a exam grader using C#. I'm new to this and don't know very much. What code would I use to add min and max buttons and to add a label stating whether it's a min or max?
private void btnAdd_Click(object sender, EventArgs e)
{
int points;
try
{
points = int.Parse(txtPoints.Text);
lstPoints.Items.Add(points);
txtPoints.Clear();
txtPoints.Focus();
if (lstPoints.Items.Count == 12)
{
txtPoints.Enabled = false;
btnAdd.Enabled = false;
}
if (lblResult.Text != "")
{
lblResult.Text = "";
}
}
catch
{
MessageBox.Show("Please enter only whole numbers");
txtPoints.Clear();
txtPoints.Focus();
}
}
private void btnAvg_Click(object sender, EventArgs e)
{
double total = 0;
for (int i = 0; i < lstPoints.Items.Count; i++)
{
total += (int)lstPoints.Items[i];
}
total /= lstPoints.Items.Count;
lblResult.Text = total.ToString();
}
private void btnClear_Click(object sender, EventArgs e)
{
lstPoints.Items.Clear();
txtPoints.Enabled = true;
btnAdd.Enabled = true;
}
}
}
hope this works
private void getMax()
{
int max=0;
for (int i = 0; i < lstPoints.Items.Count; i++)
{
if(max<(int)lstPoints.Items[i])
{
max=(int)lstPoints.Items[i];
}
}
lblResult.Text = max.ToString();
}
}
private void getMin()
{
int min=(int)lstPoints.Items[0];
for (int i = 1; i < lstPoints.Items.Count; i++)
{
if(min>(int)lstPoints.Items[i])
{
min=(int)lstPoints.Items[i];
}
}
lblResult.Text = min.ToString();
}
}
There are two possiblities as I see:
1) When you are writing this:
lstPoints.Items.Add(points);
Instead of adding to List(Of Integer) use SortedList. So the
list will always have the sorted result sets.
2) Use Array.Sort() to sort the records.
Once you have sorted records the first one is the minimum and the last one is the maximum (Assuming sorted in ascending order).
Take out two buttons and placed on the form, set Text Property from property window to Min and Max respectively and in event handler handle the Click event and pick the relevant resultset from lstPoints array.
Hope it helps!
there is a listView,label which shows the current count of a list called Name, and there is a event that raise when list count goes beyond 5. but when ever i enter the names in textbox (it goes to the list) it shows the count but the list view is not getting them properly.EX- when i enter the first name label shows the count as 1 and list view shows the name i entered in textbox and WHEN I ENTERED THE SECOND NAME label shows the list counter correctly but list view adds not only the second item i entered but also the first i have enetered before. then there is three items in list view. here is my code
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
List<string> emps = new List<string>();
private void button1_Click(object sender, EventArgs e)
{
listMake lm = new listMake();
lm.ListItemAdded += new listMake.listMethods(lm_ListItemAdded);
lm.adding(emps, textBox1,listView1);
label1.Text = emps.Count.ToString();
}
void lm_ListItemAdded(List<string> names)
{
MessageBox.Show("it enough i think its more than 5");
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
class listMake
{
public delegate void listMethods(List<string> names);
public event listMethods ListItemAdded;
public List<string> Name = new List<string>();
public void adding(List<string>Name,TextBox t1,ListView l1)
{
try
{
if (t1.Text != "")
{
Name.Add(t1.Text);
for (int i = 0; i < Name.Count; i++)
{
l1.Items.Add(Name[i]);
}
if (Name.Count > 5)
ListItemAdded(Name);
}
}
catch (Exception er) { MessageBox.Show(er.StackTrace); ;}
}
}
Problem : You are not clearinig the ListView Items each time you add items from the list.
Solution 1 : you need to clear the items from listview before adding into it.
if (t1.Text != "")
{
Name.Add(t1.Text);
l1.Items.Clear(); //add this statement
for (int i = 0; i < Name.Count; i++)
{
l1.Items.Add(Name[i]);
}
if (Name.Count > 5)
ListItemAdded(Name);
}
Solution 2: You can add only the item enetered in TextBbox instead of adding all items from begining.so you needto remove the for-loop here.
if (t1.Text != "")
{
Name.Add(t1.Text);
l1.Items.Add(t1.Text);
if (Name.Count > 5)
ListItemAdded(Name);
}
I am working on a quiz project which also allows user to add new questions.
I have an array list in which questions are stored and retrieved for displaying. Each object saved in the array list contains 5 strings.
Question
Right answer
Wrong answer 1
Wrong answer 2
Wrong answer 3
How can I randomly select objects from the array list to be displayed on the screen? And how can I shuffle the 4 answers (as radio buttons) so that the right answer appears at different positions each time?
namespace quiz
{
public partial class Quiz : Form
{
private ArrayList Questionslist;
public Quiz(ArrayList list)
{
InitializeComponent();
Questionslist = list;
}
int index = 0;
private void Quiz_Load(object sender, EventArgs e)
{
//creating an object of class Question and copying the object at index1 from arraylist into it
Question q = (Question)Questionslist[index];
//to display the contents
lblQs.Text = q.Quest;
radioButtonA1.Text = q.RightAnswer;
radioButtonA2.Text = q.WrongAnswer1;
radioButtonA3.Text = q.WrongAnswer2;
radioButtonA4.Text = q.WrongAnswer3;
}
private int Score = 0;
private void radioButtonA1_CheckedChanged(object sender, EventArgs e)
{
//if checkbox is checked
//displaying text in separate two lines on messagebox
if (radioButtonA1.Checked == true)
{
MessageBox.Show("Well Done" + Environment.NewLine + "You Are Right");
Score++;
index++;
if (index < Questionslist.Count)
{
radioButtonA1.Checked = false;
radioButtonA2.Checked = false;
radioButtonA3.Checked = false;
radioButtonA4.Checked = false;
Quiz_Load(sender, e);
}
else
{
index--;
MessageBox.Show("Quiz Finished" + Environment.NewLine + "your Score is" + Score);
Close();
}
}
}
private void radioButtonA2_CheckedChanged(object sender, EventArgs e)
{
if (radioButtonA2.Checked == true)
{
MessageBox.Show("Sorry" + Environment.NewLine + "You Are Wrong");
Close();
}
}
private void radioButtonA3_CheckedChanged(object sender, EventArgs e)
{
if (radioButtonA3.Checked == true)
{
MessageBox.Show("Sorry" + Environment.NewLine + "You Are Wrong");
Close();
}
}
private void radioButtonA4_CheckedChanged(object sender, EventArgs e)
{
if (radioButtonA4.Checked == true)
{
MessageBox.Show("Sorry" + Environment.NewLine + "You Are Wrong");
Close();
}
}
}
}
this is the code for class question
namespace quiz
{
[Serializable()]
public class Question : ISerializable
{
public String Quest;
public String RightAnswer;
public String WrongAnswer1;
public String WrongAnswer2;
public String WrongAnswer3;
public Question()
{
Quest = null;
RightAnswer=null;
WrongAnswer1=null;
WrongAnswer2=null;
WrongAnswer3=null;
}
//serialization function
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Question", Quest);
info.AddValue("Right Answer", RightAnswer);
info.AddValue("WrongAnswer1",WrongAnswer1);
info.AddValue("WrongAnswer2",WrongAnswer2);
info.AddValue("WrongAnswer3",WrongAnswer3);
}
//deserializaton constructor
public Question(SerializationInfo info, StreamingContext context)
{
Quest = (String)info.GetValue("Question", typeof(String));
RightAnswer=(String)info.GetValue("Right Answer",typeof(String));
WrongAnswer1=(String)info.GetValue("WrongAnswer1",typeof(String));
WrongAnswer2=(String)info.GetValue("WrongAnswer2",typeof(String));
WrongAnswer3=(String)info.GetValue("WrongAnswer3",typeof(String));
}
}
}
You know which one is the 'right' answer based on the Text property. One approach would be to store the answers in an array and shuffle the array before assigning it to the radio buttons:
// You're going to make questionList a List<Question> because if you like
// Right answers you know that ArrayList is Wrong.
Question q = questionList[index];
// You should move this next bit to the Question class
string[] answers = new string[]
{
q.RightAnswer,
q.WrongAnswer1,
q.WrongAnswer2,
q.WrongAnswer3
};
// Using the Fisher-Yates shuffle from:
// http://stackoverflow.com/questions/273313/randomize-a-listt-in-c-sharp
Shuffle(answers);
// Ideally: question.GetShuffledAnswers()
radioButtonA1.Text = answers[0];
radioButtonA2.Text = answers[1];
radioButtonA3.Text = answers[2];
radioButtonA4.Text = answers[3];
Then later, all you need is a single radio button event handler that they all share:
private void radioButton_CheckedChanged(object sender, EventArgs e)
{
RadioButton rb = (RadioButton)sender;
Question q = questionList[index];
//if checkbox is checked
//displaying text in separate two lines on messagebox
if (rb.Checked && q.RightAnswer == rb.Text)
{
// Move your code to another method
// MessageBox.Show("Well Done" + Environment.NewLine + "You Are Right");
UserSelectedCorrectAnswer();
}
else if (rb.Checked)
{
// They checked the radio button, but were wrong!
// MessageBox.Show("Sorry" + Environment.NewLine + "You Are Wrong");
UserSelectedWrongAnswer();
}
}
Pick a random string from ArrayList:
string randomPick(ArrayList strings)
{
return strings[random.Next(strings.Length)];
}
You might consider having a Question class. That would contain a string Text member for the question (because I think Question.Question is silly, Question.Text makes more sense when read). Since you mentioned an ArrayList (not because I think it's the best solution), you can have one of those as a member as well which would contain all the potential answers.
When you create a question, you can display Question.Text, then use the function above to randomly pick an answer. Then use RemoveAt(index) on the answers to ensure you don't duplicate answers.
I'd make a Question class containing the question, the right answer and a list of possible answers, and have methods that return the possible answers in randomized order and compares a guess to the correct answer. It could look something like this:
class Question
{
String question;
String rightAnswer;
List<String> possibleAnswers = new ArrayList<String>();
public Question(String question, String answer, List<String> possibleAnswers)
{
this.question = question;
this.rightAnswer = answer;
this.possibleAnswers.addAll(possibleAnswers);
this.possibleAnswers.add(this.rightAnswer);
}
public List<String> getAnswers()
{
Collections.shuffle(possibleAnswers);
return possibleAnswers;
}
public boolean isCorrect(String answer)
{
return answer.equals(correctAnswer);
}
}
You should encapsulate all the questions in a class. For example, create a class named Question. This class can contain 5 variables: String question, String answer1, String answer2, String answer3 and String answer4.
Save all your questions in a database or read them from a file and load them on the start of the program.
Use the Random class to randomly select a question and to 'shuffle' the 4 questions.
Here's a method that will work:
public List<string> Randomize(string[] numbers)
{
List<string> randomized = new List<string>();
List<string> original = new List<string>(numbers);
Random r = new Random();
while (original.Count > 0) {
int index = r.Next(original.Count);
randomized.Add(original[index]);
original.RemoveAt(index);
}
return randomized;
}
just adapt it to string array instead of int array
The shuffle is probably a standard question, but I guess this will work:
// Warning: Not a thread-safe type.
// Will be corrupted if application is multi-threaded.
static readonly Random randomNumberGenerator = new Random();
// Returns a new sequence whose elements are
// the elements of 'inputListOrArray' in random order
public static IEnumerable<T> Shuffle<T>(IReadOnlyList<T> inputListOrArray)
{
return GetPermutation(inputListOrArray.Count).Select(x => inputListOrArray[x]);
}
static IEnumerable<int> GetPermutation(int n)
{
var list = Enumerable.Range(0, n).ToArray();
for (int idx = 0; idx < n; ++idx)
{
int swapWith = randomNumberGenerator.Next(idx, n);
yield return list[swapWith];
list[swapWith] = list[idx];
}
}
If you don't have IReadOnlyList<T> (.NET 4.5), just use IList<T>. The incoming object is not mutated.
thanks to all of u who answered my question.i did it this way.
private void Quiz_Load(object sender, EventArgs e)
{
displayQs();
}
private void displayQs()
{
Random _random = new Random();
int z = _random.Next(Questions.Count);
Question q = (Question)Questions[z];
Qslbl.Text = q.Quest;
DisplayAns(q, _random);
}
private void DisplayAns(Question q, Random _random)
{
int j = 0;
int[] array = new int[4];
for (int i = 0; j <= 3; i++)
{
int x = _random.Next(4);
x++;
if (array.Contains(x))
{
continue;
}
else
{
array[j] = x;
j++;
string answer = null;
if (j == 1)
answer = q.RightAnswer;
else if (j == 2)
answer = q.WrongAnswer1;
else if (j == 3)
answer = q.WrongAnswer2;
else if (j == 4)
answer = q.WrongAnswer3;
if (x == 1)
radioButton1.Text = answer;
else if (x == 2)
radioButton2.Text = answer;
else if (x == 3)
radioButton3.Text = answer;
else if (x == 4)
radioButton4.Text = answer;
}
}
}
I have a class called "Player" with a constructor that takes 2 strings and an int.
These are declared in Textboxes on a form, with each team (Home H / Away A) having a different name, and each name type (Last L / First F) adding to the textbox's name. therefor giving a unique name such as txtFHome1.
In a foreach loop I want to create a new Player with the details of the textboxes on a page.
This is what I have got so far.
List <Player> HomeTeam = new List<Player>
private void btnAccept_Click(object sender, EventArgs e)
{
foreach (Control c in this.Controls)
{
for (int i = 0; i < 10; i++)
{
HomeTeam.Add (new Player(c.Name.EndsWith("FHome"+i),c.Name.EndsWith("LHome"+i),c.Name.EndsWith("upDownH"+i)));
}
}
}
any help?
From you post I understand that there are always 3 controls for 11 players, so there is no need to iterate trough all Controls in the form.
private void btnAccept_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
var player = new Player(((TextBox)this.Controls.FindControl("FHome" + i)).Text, ((TextBox)this.Controls.FindControl("LHome" + i)).Text, ((NumericUpDown)this.Controls.FindControl("upDownH" + i)).Value);
HomeTeam.Add(player);
}
}
The first way is to use dictionaries with all controls. It is the fastest and easiest way.
Dictionary<int, TextBox> FHome;
Dictionary<int, TextBox> LHome;
Dictionary<int, TextBox> upDownH;
You should add the controls like this:
FHome.Add(0, textBoxLHome0);
FHome.Add(1, textBoxLHome1);
Then in your code you can use
for (int i = 0; i < 10; i++)
{
HomeTeam.Add (new Player(FHome[i].Text, LHome[i].Text, upDownH[i].Text));
}
try this:
Controls.OfType<TextBox>().ToList().ForEach((textbox) =>
{
for (int i = 0; i < 10; i++)
{
textbox.Text = "your text";
}
});
remember to include using System.Linq
I advice you to at lease try an encapsulate the three textboxes into a single UserControl like so:
public partial class ControlPlayerParams : UserControl {
public string Param1 { get { return this.textBox1.Text; } }
public string Param2 { get { return this.textBox2.Text; } }
public string Param3 { get { return this.textBox3.Text; } }
public ControlPlayerParams() {
this.InitializeComponent();
}
}
That way, you could at least do what you wanted to do more fluently and more safely (plus you get to modify just one UserControl in case you need something changed (Validation ??)):
foreach (ControlPlayerParams cpp in this.Controls.OfType<ControlPlayerParams>())
HomeTeam.Add(new Player(cpp.Param1, cpp.Param2, cpp.Param3));
BUT you should rethink the architecture of the app a bit if you ask me.