Here's my issue...I have a list of servers, each with an ID and ServerName. I want to be able to select a server from the ComboBox and edit it in place, then have its ID available to update via SQL later. So let's say this is the data: (ID=1, Name="Server1"), (ID=2, Name="Server2"), (ID=3, Name="Server3"). If I select Server3 from the ComboBox, I'd like to edit it to be "Server4" then upload that with a SQL query (I know how to do this part). I'm utilizing MVVM, so all the values are properties of my ViewModel.
Currently, when the text field is modified in the ComboBox the SelectedServer immediately becomes null, presumably because it is no longer a value it recognizes. I could use some guidance on how to get this to do what I'm trying to do.
<ComboBox Grid.Column="1" x:Name="serverNameUpdateBox" HorizontalAlignment="Stretch" Height="23" VerticalAlignment="Center" IsEditable="True"
ItemsSource="{Binding Path=DataContext.SelectedProjectServers, ElementName=main}"
DisplayMemberPath="ServerName"
SelectedValue="{Binding SelectedServer}"
SelectedValuePath="ServerName"
Text="{Binding SelectedServer.ServerName, UpdateSourceTrigger=LostFocus}"
/>
And ViewModel relevant code:
namespace ViewModel
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
SelectedProjectServers = new List<Server>();
SelectedServer = new Server();
private Server _selectedServer;
public Server SelectedServer
{
get { return _selectedServer; }
set
{
if (value == null) { ModifiedServer = _selectedServer; }
_selectedServer = value;
RaisePropertyChanged("SelectedServer");
}
}
private List<Server> _selectedProjectServers;
public List<Server> SelectedProjectServers
{
get { return _selectedProjectServers; }
set
{
_selectedProjectServers = value;
RaisePropertyChanged();
}
}
}
}
}
And Model relevant code:
namespace Model
{
public class Server : INotifyPropertyChanged
{
private string _serverName;
public string ServerName
{
get { return _serverName; }
set
{
_serverName = value;
RaisePropertyChanged();
}
}
private int _serverID;
public int ServerID
{
get { return _serverID; }
set
{
_serverID = value;
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string caller = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
}
}
Bind a property like "EditedServerName" to Combobox.Text.
When the "EditedServerName" is changed you can set the value to the "ServerName" of your SelectedServer.
<ComboBox Grid.Column="1" x:Name= "serverNameUpdateBox" HorizontalAlignment= "Stretch" Height= "23" VerticalAlignment= "Center" IsEditable= "True"
ItemsSource= "{Binding Path=DataContext.SelectedProjectServers, ElementName=main}"
DisplayMemberPath= "ServerName"
SelectedItem="{Binding SelectedServer}"
Text= "{Binding EditedServerName, UpdateSourceTrigger=LostFocus}"
/>
ComboBox is primarily used for selection. You could have used another control like datagrid or so for update functionality.
Well, if you want to do it the ComboBox way, I suggest that you place a couple of text boxes below your combo box and bind these text boxes content to the SelectedServer properties i.e.
<TextBox x:name="ServerName" Text ={Binding SelectedServer.ServerName} />
and so on.
So, when ever a server is selected, these text boxes will be filled with values from currently selected Server. And then, you can trigger some command using a button below these boxes which triggers the sql query and passes the required data using bound properties from text boxes.
I hope you got the idea.
Related
I want to bind a textbox to a selected DataGrid. I have already binded the list to a datagrid but now I would like to bind the TextBoxtext to the DataGridselected row so its content will be placed into the TextBox
txtOccArea.DataContext = hegData;
//hegData is a list of an object
Thanks!
You should create a new class like this ( I hope you are using MVVM ).
public class YourViewVM : INotifyPropertyChanged
{
#region Fields
private object selectedDataGridCell;
private string textBoxContent;
private List<YourObject> dataGridSource;
#endregion
#region Properties
public object SelectedDataGridCell
{
get
{
return this.selectedDataGridCell;
}
set
{
if (this.selectedDataGridCell != value)
{
this.selectedDataGridCell = value;
OnPropertyChanged("SelectedDataGridCell");
}
}
}
public string TextBoxContent
{
get
{
return this.textBoxContent;
}
set
{
if (this.textBoxContent != value)
{
this.textBoxContent = value;
OnPropertyChanged("TextBoxContent");
}
}
}
public List<YourObject> DataGridSource
{
get
{
return this.dataGridSource;
}
set
{
if (this.dataGridSource != value)
{
this.dataGridSource = value;
OnPropertyChanged("Source");
}
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In your view, just modify it to:
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding DataGridSource}" SelectedItem="{Binding SelectedDataGridCell}" />
<TextBox Grid.Row="1" Text="{Binding TextBoxContent}"></TextBox>
</Grid>
You need to add the INotifyPropertyChanged so the TextBox knows when the selection has changed.
If you need to set the DataGridSource to your hegData list, just create a constructor and set the property there like this:
public YourViewVM(List<YourObject> hegData)
{
this.DataGridSource = hegData;
}
And where you create it just call it like:
YourViewVM yourViewVM = new YourViewVM(hegData)
If you just want to display the value in the TextBox it will be ok to bind in XAML. Try this:
<DataGrid x:Name="MyGrid" ItemsSource="{Binding hegData}"/>
<TextBox Text={Binding SelectedItem, ElementName=MyGrid}/>
If you actually need to alter the Selected Item, I think you should define a SelectedListItem property in your ViewModel and bind the TextBox's text to this property.
ViewModel:
public List<object> hegData {get;set;}
public object SelectedListItem {get;set;}
View:
<DataGrid ItemsSource="{Binding hegData}"
SelectedItem="{Binding SelectedListItem}"/>
<TextBox Text={Binding SelectedListItem}/>
So many examples found and none fit! My list box is a list of Result objects. Results can be checked or unchecked in a listbox to mark them as 'Allowed to 'transmit.
<ListBox
x:Name="FileListBox"
ItemsSource="{Binding TestResults}"
ItemTemplate="{StaticResource FileListTemplate}"
SelectionMode="Single"
SelectedItem="{Binding FileListSelected}"
Background="#FFFFFBE2" />
The FileListTemplate
<DataTemplate x:Key="FileListTemplate">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".2*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding FileName}" />
<TextBlock Grid.Column="1"
Text="Machine">
</TextBlock>
<CheckBox x:Name="UploadOK"
Grid.Column="2"
HorizontalAlignment="Right"
IsChecked="{Binding CanUpload, Mode=TwoWay}" />
</Grid>
</DataTemplate>
I took out a lot of formatting code to reduce the clutter. So when the check box is checked (or un checked) I need to set a boolean on the object to true or false. But I do not want the ListItem selected just because the checkbox is selected. When the ListItem is selected something else happens. Here is the code for that.
public TestResult FileListSelected
{
get
{
return selectedItem;
}
set
{
if (value == selectedItem)
return;
selectedItem = value;
if (!Workspaces.Any(p => p.DisplayName == value.FileName))
{
this.DisplayTestResult(value as TestResult);
}
base.RaisePropertyChanged("FileListSelected");
}
}
And here is the code I bound to for the Checkbox (although it didn't work).
public bool CanUpload
{
get { return selectedItem.CanUpload; }
set
{
selectedItem.CanUpload = value;
}
}
I appreciate you looking at this.
Internal Class TestResult
{
...
private bool _canUpload;
public bool CanUpload
{
get { return _canUpload; }
set
{
_canUpload = value;
base.RaisePropertyChanged("CanUpload");
}
}
}
When working with MVVM always check for the following:
Add using System.ComponentModel; to your ViewModelClass
Inherit from INotifyPropertyChanged
Always check your DataContext and see the Output Window for BindingErrors
Create Bindings like this:
Example Property:
public string Example
{
get { return _example; }
set
{
_example= value;
OnPropertyChanged();
}
}
this will call OnPropertyChanged automatically every time a new value is assigned (not updated automaticaly once it changes from some other location!)
Make sure your Implementation of INotifyPropertyChanged looks like this:
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
for that you also need using System.Runtime.CompilerServices;
Other options to get your code working:
Your TestResults sould be an ObservableCollection<TestResult>
TestResult should have a property for CanUpload and FileName and inherit from INotifyPropertyChanged
Then on your MainViewModel for example on and ButtonClick your can get the selected files like this:
private List<string> GetSelectedFiles()
{
return TestResults.Where(result => result.CanUpload == true).Select(r => r.FileName).ToList());
}
Note:
FileListSelected is a Property of your ListBox's DataContext which is different to the DataContext of an entry (or at least should be).
FileListSelected will then return the selected Item of your ItemsSource.
Maybe you can comment on this problem with the row selection/checkbox check and add some detail so I can help you more.
EDIT: Notify MainWindowViewModel about CheckBox State Changes:
I see two possible approaches here:
USING EVENT
Add this to your TestResult class:
public delegate void CheckBoxStateChangedHandler(object sender, CheckBoxStateChangedEventArgs e);
public event CheckBoxStateChangedHandler CheckBoxStateChanged;
public class CheckBoxStateChangedEventArgs
{
bool CheckBoxChecked { get; set; }
}
Make sure that on creation of a new TestResult in your MainViewModel you subscribe to that event;
testResult.CheckBoxStateChanged += CheckBox_StateChanged;
Handle what you want to do once the state is changed in CheckBox_StateChanged. Note that the argument e contains the boolean (Checked) and the corresponding TestResult as the sender.
You simply invoke your new Event in the Setter of your CheckBox.Checked Binding:
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
OnPropertyChanged();
CheckBoxStateChanged.Invoke(this, new CheckBoxStateChangedEventArgs() { CheckBoxChecked = value })
}
}
CALL METHOD ON MAINWINDOWVIEWMODEL
for that you need o create a static object of your MainWindowViewModel (in your MainViewModel) - don't forget to assigne a value once you create your MainWindowViewModel.
public static MainViewModel Instance { get; set; }
then simply add a public Method as you need:
public void CheckBoxValueChanged(bool value, TestResult result)
{
//Do whatever
}
you can also call in from the same spot as the event from above is invoked.
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
OnPropertyChanged();
MainWindowViewModel.Instance.CheckBoxValueChanged(value, this);
}
}
I have a Combobox with a concate string in displaymemberpath ('DescriptionComplete' in code-behind below), and that is what i get in the editable field BUT this is only the IdEchantillon that i want into the editable field...
How must i do please ?
XAML:
<ComboBox x:Name="cbEchantillon" SelectedValue="{Binding CurrentEchantillon.IdEchantillon}" ItemsSource="{Binding OcEchantillon}" DisplayMemberPath="DescriptionComplete" SelectedValuePath="IdEchantillon" SelectedItem="{Binding CurrentEchantillon}" Text="{Binding IdEchantillon}" IsEditable="True" Width="355" FontSize="14" FontFamily="Courier New" SelectionChanged="cbEchantillon_SelectionChanged"></ComboBox>
Code behind:
public class Echantillon : ViewModelBase
{
private string _IdEchantillon;
private string _Description;
private DateTime _dateechan;
private string _descriptionComplete;
}
public string IdEchantillon
{
get { return _IdEchantillon; }
set { _IdEchantillon = value; RaisePropertyChanged("IdEchantillon"); }
}
public string Description
{
get { return _Description; }
set { _Description = value; RaisePropertyChanged("Description"); }
}
public DateTime Dateechan
{
get { return _dateechan; }
set { _dateechan = value; RaisePropertyChanged("Dateechan"); }
}
public string DescriptionComplete
{
get { return string.Format("{0} {1} {2}", IdEchantillon.PadRight(20), Dateechan.ToShortDateString(), Description); }
set { _descriptionComplete = value; }
}
}
At first, please, cleanup your ComboBox property bindings. You should not create binding for SelectedValue, because you already have binding for SelectedItem property. The SelectedValue is determined by extracting the value by SelectedValuePath from the SelectedItem.
In case of binding to ViewModel, you should not also bind Text property. Use ItemsSource as you do, and set DisplayMemberPath property to what you want to show as the text representation of the every item in the bound collection.
How can I get the text of a RadAutoCompleteBox using RadControls Q1 2013 in C#?
autoCompleteBox.SelectedItem returns "ServerCrafterTelerikWPF.Command".
Edit 1:
Here's my XAML:
<telerik:RadAutoCompleteBox x:Name="txtboxCommand" ItemsSource="{Binding Commands, Source={StaticResource ViewModel}}"
DisplayMemberPath="ACommand" AutoCompleteMode="Append" HorizontalAlignment="Left"
telerik:StyleManager.Theme="Modern" Margin="280,405,0,0"
VerticalAlignment="Top" Width="330" Height="30" KeyDown="txtboxCommand_KeyDown"/>
And I don't have any C# code. I just want, when a button is pressed, to get the text that is in the RadAutoCompleteBox.
Edit 2:
And here's my collection:
public class Command
{
public string ACommand { get; set; }
}
/// <summary>
/// A view model for MainWindow.xaml
/// </summary>
public class ViewModel
{
public ObservableCollection<Command> Commands { get; set; }
public ViewModel()
{
Commands = new ObservableCollection<Command>()
{
new Command() {ACommand = "stop "},
// Other commands...
// ...
// ...
};
}
}
You should take it from the SelectedItem property. Cast it to your class and then get it from MyClass.ACommand
And I suggest binding SelectedItem with Mode=TwoWay in your ViewModel can help a lot.
Just add a Member to ViewModel which is implementing Command like:
private Command _SelectedItem;
public Command SelectedItem
{
//get set with INotifyPropertyChanged
}
Then from the xaml: Bind RadAutoCompleteBox's SelectedItem Property like:
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
I have reproduced the problem.
Yes. I had the same problem. And I found the problem and the answer too.
I got the problem because of using of type string for the selected item in my view model.
private string selectedCommand;
public string SelectedCommand
{
get
{
return selectedCommand;
}
set
{
selectedCommand = value;
NotifyPropertyChanged("SelectedCommand");
}
}
Use the type as Command class and your problem will be solved.
private Command selectedCommand;
public Command SelectedCommand
{
get
{
return selectedCommand;
}
set
{
selectedCommand = value;
NotifyPropertyChanged("SelectedCommand");
}
}
Bind the SelectedItem property of the RadAutoCompleteBox in the XAML
<telerik:RadAutoCompleteBox
x:Name="txtboxCommand"
ItemsSource="{Binding Commands, Source={StaticResource ViewModel}}"
DisplayMemberPath="ACommand"
AutoCompleteMode="Append"
HorizontalAlignment="Left"
telerik:StyleManager.Theme="Modern"
Margin="280,405,0,0"
VerticalAlignment="Top"
Width="330"
Height="30"
KeyDown="txtboxCommand_KeyDown"
SelectedItem="{Binding SelectedCommand, Mode=TwoWay}"/>
If you wanna get the selected item by the code-behind, convert the selected item to the Command class type.
var selectedItem = autoCompleteBox.SelectedItem as Command;
And actually there can be multiple selected items. In that case you have to define a collection of Command objects.
private ObservableCollection<Command> selectedCommands;
public ObservableCollection<Command> SelectedCommands
{
get
{
return selectedCommands;
}
set
{
selectedCommands = value;
NotifyPropertyChanged("SelectedCommands");
}
}
And bind it to the SelectedItems property (plural of SelectedItem) of the RadAutoCompleteBox control.
SelectedItems="{Binding SelectedCommands, Mode=TwoWay}"
And make sure you have initiated the SelectedItems.
this.SelectedCommands = new ObservableCollection<Command>();
The SearchText property of the RadAutoCompleteBox should provide you the value.
According to the documentation it gets or sets the string that is into the TextBox part of the RadAutoCompleteBox. The SearchText value is used to filter the RadAutoCompleteBox' ItemsSource.
If you want to get the "Text" of the selected item of the AutocompleteBox, then you need to cast it to the specified type. In your case it is of type ServerCrafterTelerikWPF.Command.
var selectedItem = autoCompleteBox.SelectedItem;
if (selectedItem is ServerCrafterTelerikWPF.Command) {
var selectedCommand = selectedItem as ServerCrafterTelerikWPF.Command;
string textOfAutoCompleteBox = selectedCommand.ACommand;
}
Tried all the solutions for similar issues around here, still no go. I have a ComboBox that should work for selection of existing items and/or for adding new ones. Only the selected item part works. Category is just an object with a Name and Id.
Thanks in advance!
XAML
<ComboBox Name="CbCategory" ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory.Name, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding NewCategory.Name}" DisplayMemberPath="Name"
IsEditable="True"/>
Code behind
private Category _selectedCategory;
public Category SelectedCategory
{
get { return _selectedCategory; }
set
{
if (Equals(_selectedCategory, value)) return;
_selectedCategory = value;
SendPropertyChanged("SelectedCategory");
}
}
private Category _newCategory;
public Category NewCategory
{
get { return _newCategory; }
set
{
if (Equals(_newCategory, value)) return;
_newCategory = value;
SendPropertyChanged("NewCategory");
}
}
Your Text Binding doesn't work because you're binding against a null Category property. Instantiate it instead.
public Category NewCategory
{
get { return _newCategory ?? (_newCategory = new Category()); }
set
{
if (Equals(_newCategory, value)) return;
_newCategory = value;
SendPropertyChanged("NewCategory");
}
}
Edit: Elaborating as per your comment:
Your ComboBox.Text binding is set to "{Binding NewCategory.Name}", so no matter what the value of SelectedCategory is, the Text property will always reflect the name of the NewCategory.
When NewCategory is null, the Text property has nothing to bind to, and therefore the 2-way binding cannot be executed (that is, the value of the Text property cannot be passed back to NewCategory.Name, because that will cause an NullReferenceException (because the NewCategory is null).
This does not affect the case of the SelectedItem, because that's binding directly to the SelectedCategory property, and not a sub-property of that.
Create new varible to keep text of combobox. If the selectedItem having null value get the text of combobox as new Item,
Code :
<ComboBox Name="CbCategory" ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory.Name, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding Name}" DisplayMemberPath="Name"
IsEditable="True"/>
private String _name;
public Category Name
{
get { return _name; }
set
{
_name = value
SendPropertyChanged("Name");
}
}
public ICommand ItemChange
{
get
{
`return new RelayCommand(() =>`{
try{string item = this.SelectedCategory.Code;}
catch(Exception ex){string item = this.Name;}
}, () => { return true; });
}
}