Maybe this is a very simple question for you .NET guys but since I come from a MFC C++ dev teams, this behavior all got our dev team wondering what was going on. (In MFC, when we create a dropdown datasource, the source is shared but the value of each dropdown is not affected)
It's a very simple situation. I have 3 dropdownlist in my form that ask for streets. The street you are on and the others 2 closest streets. Each of these dropdownlist have the same datasource. So what we did is that (by the way ComboList is just a class that inherit ListItem with some more functions)
//Combo Street
ComboList cboNomRue = new ComboList();
Tools.GetCombo(cboNomRue, Tools.ComboTypeRt.RT_NOM_RUE, true, true);
ddlNomRue.DisplayMember = "Numero";
ddlNomRue.ValueMember = "ID";
ddlNomRue.DataSource = cboNomRue;
//Combo Street From
ddlDe.DisplayMember = "Numero";
ddlDe.ValueMember = "ID";
ddlDe.DataSource = cboNomRue;
//Combo Street To
ddlA.DisplayMember = "Numero";
ddlA.ValueMember = "ID";
ddlA.DataSource = cboNomRue;
Using this code, when the user change the value in ddlNomRue, the 3 dropdownlist change to that value! So to fix that, I did that.
//Combo Street
ComboList cboNomRue = new ComboList();
Tools.GetCombo(cboNomRue, Tools.ComboTypeRt.RT_NOM_RUE, true, true);
ddlNomRue.DisplayMember = "Numero";
ddlNomRue.ValueMember = "ID";
ddlNomRue.DataSource = cboNomRue;
ComboList cboNomRue2 = new ComboList(cboNomRue);
ComboList cboNomRue3 = new ComboList(cboNomRue);
//Combo Street From
ddlDe.DisplayMember = "Numero";
ddlDe.ValueMember = "ID";
ddlDe.DataSource = cboNomRue2;
//Combo Street To
ddlA.DisplayMember = "Numero";
ddlA.ValueMember = "ID";
ddlA.DataSource = cboNomRue3;
I don't like this since it's duplicating variables when only one is really needed. Is this the good way of dealing with this situation or is there anything else I could do?
Thanks
I am not clear about the ComboList Class used by you, but if you use the List to store the item which is to be displayed in comboBox is the better option.
The way adopted by you is not good because of creating object again and again. To overcome this, use List and just call it function AsReadOnly();
Example:
List<string> Mylist = new List<string>();
Mylist.Add("salman");
Mylist.Add("khan");
Mylist.Add("yousafzai");
Mylist.Add("ranizai");
Mylist.Add("kachokhail");
this.tUsersTableAdapter.Fill(this.dbAIASDataSet.tUsers);
comboBox1.DataSource = Mylist.AsReadOnly();
comboBox1.DisplayMember = "ID";
comboBox1.ValueMember = "ID";
comboBox2.DataSource = Mylist.AsReadOnly();
comboBox2.DisplayMember = "ID";
comboBox2.ValueMember = "ID";
But if you must have to use the Object of ComboList then implement the interface icloneable in ComboList or use any other way to create a shallow copy.
The form automatically create a default BindingContext for all objects on the form that support data binding. The BindingContext allows the form to know the count and current position of the collection entries within that object. When you select an item on the first list, you change its position and since the other two controls are bound with the same BindingContext, they all change as well. To fix this behavior, simply create a different BindingContext for each control as follow:
ddlNomRue.BindingContext = new BindingContext();
ddlDe.BindingContext = new BindingContext();
ddlA.BindingContext = new BindingContext();
Now you can bind the same ComboList to all three controls and you will still get the behavior that you want.
Related
I am using a LINQ To SQL class, which is initialized before the constructor (the tables are added to it, it works):
DataClasses1DataContext dc = new DataClasses1DataContext();
Then I have a listbox:
listBox1.DataSource = dc.MyTable;
listBox1.DisplayMember = "Name";
listBox1.ValueMember = "PersonID";
Then I want to add a new record:
MyTable mt = new MyTable();
mt.Name = "John Smith";
mt.PersonID = 30;
dc.MyTable.InsertOnSubmit(mt);
dc.SubmitChanges();
I want to refresh the listbox's datasource, by doing:
listBox1.DataSource = dc.MyTable;
...but the new record doesn't show up.
However, if I update the datasource like this:
var q = from x in dc.MyTable select x;
listBox1.DataSource = q;
This works.
My only question is: why? I saw that some people set the DataSource to null, then back to the table, but that doesn't solve my problem.
(it's a winforms project)
listBox1.DataSource = dc.MyTable.ToList() seems to solve the problem, answered by Ehsan Sajjad in the comments.
When you update your datasource by assigning the exact same object, the control will never know that something changed. If you don't want to implement MVVM, MVC or something similar, you could just clear the value:
listBox1.DataSource = null;
listBox1.DataSource = dc.MyTable;
ToList() can cause performace (https://stackoverflow.com/a/15027581/4550393) issues when having lots of items. Since you don't really need ToList() in this case the better solution would be to clear the list and to rebind it thereafter:
listBox1.Items.Clear();
listBox1.DataSource = dc.MyTable;
I am dynamically creating a Winforms multi-select ListBox and adding it into a flowpanel control. I bind a datasource from an object I created and verified that the DataSource has does in fact have approximately 14 elements. When I do a listBox.SetSelected(0, true) I get an System.ArgumentOutOfRangeException error thrown.
I have determined the problem is that while the DataSource has 14 elements, the Item collection has none (0) and is therefore throwing the exception. My question is why are these two different from one another, and why would I not simply do a foreach item in datasource add to the item collection?
The following is the code I have so far:
case InsertableItemParameter.ParameterType.ListBox:
//note: two-way bindings are not possible with multiple-select listboxes
Label lblListBox = new Label();
lblListBox.Text = param.DisplayText;
ListBox listBox = new ListBox();
listBox.DataSource = param.Values;
listBox.DisplayMember = "Value";
listBox.SelectionMode = SelectionMode.MultiExtended;
listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
listBox.SetSelected(0, true); //will throw argument out of range exception here!
listBox.SetSelected(1, true);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
flowPanel.SetFlowBreak(listBox, true);
break;
Below is an alternative solution I attempted and worked, but again why would I use DataSource versus Items collection?
case InsertableItemParameter.ParameterType.ListBox:
//note: two-way bindings are not possible with multiple-select listboxes
Label lblListBox = new Label();
lblListBox.Text = param.DisplayText;
ListBox listBox = new ListBox();
//listBox.DataSource = param.Values;
listBox.DisplayMember = "Value";
listBox.SelectionMode = SelectionMode.MultiExtended;
listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
listBox.BeginUpdate();
foreach (String paramater in param.Values)
{
listBox.Items.Add(paramater);
}
listBox.EndUpdate();
listBox.SetSelected(0, true);
listBox.SetSelected(1, true);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
flowPanel.SetFlowBreak(listBox, true);
break;
ANSWER: Thanks for all the responses. The issue here is visibility and win-form rendering. While the difference between DataSource and Items collections were not really addressed save a few people, the true source of my problem was resolved by calling the SetSelected() method after the form was done drawing. This causes a lot of problems in the design of my application which I have to resolve, but this is was the problem. See the reply I marked as the answer.
Your problem probably lies elsewhere, because this code works fine:
string[] ds = {"123","321"};
listBox1.DataSource = ds;
listBox1.SetSelected(1, true);
MessageBox.Show(listBox1.Items.Count.ToString()); //returns 2
Tested in a brand new C# project with a listBox1 put on the form, and the above code sitting in Form_Load.
EDIT: I did not realize that creating a ListBox in runtime could make a difference, and especially because it matters when to set selected items. This code works:
string[] ds = { "123", "321" };
ListBox lst = new ListBox();
lst.DataSource = ds;
lst.Size = new Size(100,100);
this.Controls.Add(lst);
//make sure to call SetSelected after adding the ListBox to the parent
lst.SetSelected(1, true);
Thanks to #Brad for pointing this out. So back on the original question, replace this:
listBox.SetSelected(0, true);
listBox.SetSelected(1, true);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
with this:
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
listBox.SetSelected(0, true);
listBox.SetSelected(1, true);
And it should work.
You have two options of how to get data to be available in a ListBox. You can set the DataSource or you can add the items manually via listBox.Items.Add(paramater). You cannot do both because they will step on each other hence your error
...cannot add items to the Item collection when DataSource is set.
Items From MSDN
This property enables you to obtain a reference to the list of items that are currently stored in the ListBox. With this reference, you can add items, remove items, and obtain a count of the items in the collection. For more information about the tasks that can be performed with the item collection, see the ListBox.ObjectCollection class reference topics.
Datasource From MSDN
An object that implements the IList or IListSource interfaces, such as a DataSet or an Array. The default is null
I'm not an expert on this matter, but from what I read it appears that Items allows you to add/modify the contents in the list whereas Datasource retrieves and sets the content.
The Items collection is populated from the DataSource only when the Control is visible. since you create your control dynamically, it is not added to the parent control and therefore not visible. Therefore you first need to have a Control that is visible on the screen. In your code you set DataSource and then set the selected items before your Control is visible on the FlowChart since it isn't added to the Parent control. You should change the sequence of the statements. You should add the listBox to the FlowPanel which will populate Items collection from the DataSource upon which you can execute SetSelected() method. Try this and note the changed order of the execution of your initial code:
ListBox listBox = new ListBox();
listBox.DataSource = param.Values;
listBox.DisplayMember = "Value";
listBox.SelectionMode = SelectionMode.MultiExtended;
listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox); //notice that you first add the listBox to the flowChart
listBox.SetSelected(0, true); //and then you have items in the Items collection which you can select
listBox.SetSelected(1, true);
Im not sure why there are two different Collections. The Items property seems more simple.
I found the reason for the exception:
apparently you have to do things in a specific order, like this:
//init the listbox
var listBox1 = new ListBox();
listBox1.Location = new System.Drawing.Point(122, 61);
listBox1.Size = new System.Drawing.Size(205, 147);
listBox1.SelectionMode = SelectionMode.MultiExtended;
Controls.Add(listBox1); //<-- point of interest
//then set the DataSource
listBox1.DataSource = lst;
listBox1.DisplayMember = "Name";
listBox1.ValueMember = "Age";
//then set the selected values
listBox1.SetSelected(0, true);
listBox1.SetSelected(1, true);
My Test class look like this:
public class Test
{
private static Random r = new Random();
public Test (string name)
{
Name = name;
Age = r.Next(16, 45);
}
public string Name { get; set; }
public int Age{ get; set; }
}
And lst is declared like this:
var lst = new List<Test>()
{
new Test("jens"),
new Test("Tom"),
new Test("John"),
new Test("Don"),
new Test("Jenny"),
};
I have a custom object with several properties, one of which returns a list. This is the code for the object:
public class SearchResult
{
private int eventId;
private String eventTitle;
private int startDate;
private List<String> tags;
// Properties
public int EventId { get { return this.eventId; } }
public String EventTitle { get { return this.eventTitle; } }
public int StartDate { get { return this.startDate; } }
public List<String> Tags { get { return this.tags; } }
public SearchResult(int eventId, String eventTitle, int startDate, List<String> tags)
{
// Constructor code
}
public List<String> GetTags()
{
return this.tags;
}
}
I also have a DataGridViewComboBoxColumn that I want to bind to the Tags property. Basically, each SearchResult object will be displayed in its own row, and I want the List<String> in the Tags property of each object to be displayed in a ComboBox cell in that row. This is the code I have so far for my DataGridView:
BindingList<SearchResult> results = new BindingList<SearchResult>();
results.Add(new SearchResult(1, "This is a title", 2012, new List<String> { "Tag1", "Tag with a long name1" }));
results.Add(new SearchResult(2, "The quick brown fox", 2012, new List<String> { "Stack", "Overflow" }));
results.Add(new SearchResult(3, "In this tutorial, you create a class that is the type for each object in the object collection. ", 2012, new List<String> { "NYSE", "FTSE" }));
results.Add(new SearchResult(4, "another long piece of title text", -999, new List<String> { "Rabbits", "Chickens" }));
MyDataGrid.AutoGenerateColumns = false;
MyDataGrid.AllowUserToAddRows = false;
MyDataGrid.AllowUserToDeleteRows = false;
MyDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None;
MyDataGrid.BackgroundColor = System.Drawing.SystemColors.Control;
MyDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
MyDataGrid.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.AutoSizeToDisplayedHeaders;
MyDataGrid.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.DisplayedCells;
MyDataGrid.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
DataGridViewTextBoxColumn eventIdColumn = new DataGridViewTextBoxColumn();
eventIdColumn.DataPropertyName = "EventId";
eventIdColumn.HeaderText = "Event ID";
eventIdColumn.ReadOnly = true;
eventIdColumn.Width = 84;
DataGridViewTextBoxColumn eventTitleColumn = new DataGridViewTextBoxColumn();
eventTitleColumn.DataPropertyName = "EventTitle";
eventTitleColumn.HeaderText = "Event Title";
eventTitleColumn.ReadOnly = true;
eventTitleColumn.Width = 368;
DataGridViewTextBoxColumn startDateColumn = new DataGridViewTextBoxColumn();
startDateColumn.DataPropertyName = "StartDate";
startDateColumn.HeaderText = "Start Date";
startDateColumn.ReadOnly = true;
startDateColumn.Width = 130;
//I think I need to insert the code for the tags column here, but I'm not sure
MyDataGrid.Columns.Add(eventIdColumn);
MyDataGrid.Columns.Add(eventTitleColumn);
MyDataGrid.Columns.Add(startDateColumn);
//MyDataGrid.Columns.Add(tagsColumn);
MyDataGrid.DataSource = results;
I derived this code from a tutorial I found online, and it works perfectly.
I've been trying to bind the Tags property of SearchResult to a DataGridViewComboBoxColumn, but I'm not sure how. I've been looking at this question, which provides this code:
column.DataPropertyName = "Foo";
column.DisplayMember = "SomeNameField";
column.ValueMember = "Bar"; // must do this, empty string causes it to be
// of type string, basically the display value
// probably a bug in .NET
column.DataSource = from foo in Foo select foo;
grid.DataSource = data;
The reason I'm having trouble is because of a few nuances of the linked question that I don't understand.
According to the documentation and the linked question, DisplayMember should be linked to the property that "contains a description of the instance", but since SearchResult objects are added dynamically and don't have any description associated with them, should I just leave it blank?
ValueMember is giving me similar problems, since I'm unsure what to put even after reading its documentation.
In the linked question, the accepted answer binds the entire datagrid at once using LINQ. Is that how I should be doing this? I'm not sure how to modify that code for my situation, but I thought it would be something along these lines.
:
tagsColumn.DataPropertyName = "Tags";
tagsColumn.DisplayMember = ""; // I'm unsure of what to put here
tagsColumn.ValueMember = ""; // Once again, I don't know what to set this to
I also presume I should have a line that sets the DataSource for the column, e.g.
tagsColumn.DataSource = <some LINQ query, perhaps?>
but I don't know because the only mostly relevant C# source I've been able to find is that question.
UPDATE:
I did find a second question that suggests code similar to this for data binding:
// reference the combobox column
DataGridViewComboBoxColumn cboBoxColumn = (DataGridViewComboBoxColumn)dataGridView1.Columns[0];
cboBoxColumn.DataSource = Choice.GetChoices();
cboBoxColumn.DisplayMember = "Name"; // the Name property in Choice class
cboBoxColumn.ValueMember = "Value"; // ditto for the Value property
Based on that, I a) added the GetTags() method to SearchResult and added this code into my DataGridView initialisation code:
DataGridViewComboBoxColumn tagsColumn = new DataGridViewComboBoxColumn();
tagsColumn.DataSource = SearchResult.GetTags(); // ERROR
tagsColumn.DisplayMember = ""; // Still not sure
tagsColumn.ValueMember = ""; // ??
However, Visual Studio gives me an error on the second line when I try to run this:
An object reference is required for the non-static field, method, or property 'SearchResult.GetTags()'
UPDATE 2:
I'm still searching around for this without success. I don't understand how with other properties (e.g. EventId) I can simply declare the data property name as EventId, and it will display in the table, but I cannot do this with ComboBox columns.
Since the objects are instantiated in a separate class and put in a list, it doesn't seem to make sense to me that I should have to loop through the entire array of objects (of which there may be several hundred) to bind the Tags property to the ComboBox column for each instance, when I don't need to loop through the list of SearchResult objects to bind other properties, e.g. EventId.
Why does this binding-properties-by-name only work for some properties and not others?
I don't quite understand why you want to use DataGridViewComboBoxColumn to display a list of elements. This column kind is designed to allow user to select one of many possibilities. It seams it is not your case because you don't have public string SelectedTag{get;set;} property to store it. As I understand your model you have many tags already selected for your SearchResult and you want to display them in grid.
As documentation states:
http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridviewcomboboxcolumn.datasource
Getting or setting this [DataSource] property gets or sets the DataSource property of the object returned by the CellTemplate property. Setting this property also sets the DataSource property of every cell in the column and refreshes the column display. To override the specified value for individual cells, set the cell values after you set the column value.
DataGridViewComboBoxColumn simply does not have capability to bind items property to data source because it assumes that there is only one list of elements that is used as data source for all rows of data grid.
I also assume that you would set ReadOnly = true property for this column as you have for all other. If so it would prevent user form seeing list of tags because drop down list would never be displayed.
If you wand to display list of strings in read only mode I would suggest to flatten this list of tags to single string:
public string Tags { get { return string.Join(", ", tags); } }
and display it in text column.
For the error , i can suggest you to make an instance of the class and then call the method as its not static or you can make your method static.
Moreover As you needs the comboboxcolumn ,
DataGridViewComboBoxColumn tagsColumn = new DataGridViewComboBoxColumn();
tagsColumn.DataSource = SearchResult.GetTags(); // ERROR
tagsColumn.DisplayMember = ""; // Still not sure
tagsColumn.ValueMember = ""; // ??
Mostly we have dropdowns for objects like Country(id,name) so DisplayMember = name will be shown as text in dropdown while ValueMember = id will be used in the referencing tables in database.But this is not your case.
Here you have a list of strings to show in dropdown , so you don't need to set them.
As written here
If the DataSource property is set to a string array, then ValueMember
and DisplayMember do not need to be set because each string in the
array will be used for both value and display.
string title = HardwareInfo.GetComputerName().ToString();
TabPage myTabPage = new TabPage(title);
// tabControl1.TabPages.Add(myTabPage);
// Create Column Headers
ListView listView2 = new ListView();
ColumnHeader columnA = new ColumnHeader();
columnA.Text = "adsasd";
columnA.Width = 185;
columnA.TextAlign = HorizontalAlignment.Left;
ColumnHeader columnB = new ColumnHeader();
columnB.Text = "asd";
columnB.Width = 185;
columnB.TextAlign = HorizontalAlignment.Left;
ColumnHeader columnC = new ColumnHeader();
columnC.Text = "asdasd";
columnC.Width = 185;
columnC.TextAlign = HorizontalAlignment.Left;
ColumnHeader columnD = new ColumnHeader();
columnD.Text = "xx";
columnD.Width = 185;
columnD.TextAlign = HorizontalAlignment.Left;
// Add columns to the ListView:
listView2.Columns.Add(columnA);
listView2.Columns.Add(columnB);
listView2.Columns.Add(columnC);
listView2.Columns.Add(columnD);
listView2.Size = new Size(800, 300);
listView2.Location = new Point(0, 0);
listView2.GridLines = true;
listView2.View = View.Details;
Here I have a copy of some of my Code, and what I am looking to do is get a list of computers on my next work, then create tabs for each computer. I have that part done perfectly fine, but the issue I am having is that, it creates the listviews with the same NAME and that is causing an obvious problem when I try and add information to those specific list views. I was wondering, how would I go about giving each listview a name of the computer for example. As you can see for my tabs I can do that, but when it comes to the list views, if i try and do the same type of assign a string title to where it says Listview listview2 It wont let me compile. I'm new to programming and I apologize if this is obvious. Thank you.
It sounds like you want to create a List<ListView> and add your listviews to it.
Depending on how you use it, you may want a dictionary instead.
If i understand the question what you want is the name variable, in this case
listView2.name = <name of listview2>
http://msdn.microsoft.com/en-us/library/system.windows.forms.listview_members(v=vs.71)
But i think you should look into using functions with a returntype of columns for those column constructor parts.
You want the to make the variable that stores the listview part of a dictionary, this way you can look up the different computers by their name, or whatever string you desire
Dictionary<string, ListView>
http://msdn.microsoft.com/en-us/library/xfhwa508.aspx
You could use a List if you don't need the lookup portion of the dictionary, but is fine with using integers as with an array
List<ListView>
http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx
I have a DataGridView that uses databinding, with manually created columns, and this works fine.
However I want the rows' BackColor to be databound as well, and so far my attempts have hit errors.
This is my latest attempt:
dataGridFileTransfer.RowHeadersVisible = false;
dataGridFileTransfer.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dataGridFileTransfer.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridFileTransfer.MultiSelect = false;
dataGridFileTransfer.ReadOnly = true;
var files = GetReceivedFiles(false).Union(FileDetails.Select(FileStatus.FailedVerification)).ToList();
dataGridFileTransfer.AutoGenerateColumns = false;
string[] displayHeaders = new string[] { COL_NAME, COL_TYPE, COL_CREATED, COL_SIZE, COL_STATUS };
string[] displayProps = new string[] { "Filename", "FileTypeDisplayName", "Created", "Size", "FileStatus" };
for (int i = 0; i < displayHeaders.Length; i++)
{
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.HeaderText = displayHeaders[i];
col.DataPropertyName = displayProps[i];
if (displayHeaders[i] == COL_CREATED)
col.DefaultCellStyle.Format = Constants.DDMMYYYHHMMSS;
dataGridFileTransfer.Columns.Add(col);
}
Binding bi = new Binding("DefaultCellStyle.BackColor", files, "DisplayColor");
dataGridFileTransfer.DataBindings.Add(bi);
dataGridFileTransfer.DataSource = files;
Which is generating an ArguementException:
"Cannot bind to the property
"DefaultCellStyle.BackColor' on the
target control. Parameter name:
PropertyName"
Is it the value of PropertyName that is wrong, or should I binding to an object other than the DataGridView? (i.e. a column?)
Or is the problem that PropertyName cannot be in the form X.Y? I thought I had seen/used this syntax before, but maybe it only works for DataMember?
Any help is much appreciated
I think the problem is files.DisplayColor. files is a collection an has no property DisplayColor but each item of the collection has. So you are trying to bind a not existing property. Further binding collection DataGridView.DataBindings allows you to data bind properties of the control, not of its rows. There is only one DataGridView.DefaultCellStyle.BackColor for all rows. So I believe you end up needing to bind the DefaultCellStyle of each row to the coresponding item from files and I am not sure if this is possible. It might be that the DataGridView creates and deletes rows as required - for example if you perform filtering - and this will destroy the data binding, too.
So, I am not sure if row coloring can be done with data binding, but I personaly doubt it. This would require some really smart logic recognicing 'bind the property DisplayColor of the object data bound to this row to the property DefaultCellStyle.BackColor of this row.'
You could surly implement such an smart data binding. While it would be a great thing, it will be quite complex, too. As a simple solution you could just use the RowPrepaint event to set the correct color for the row.