User selects something in combobox, something else is sent to dependency property - c#

In my application the user can select how a date is displayed. Most of the standard datetime format strings can be selected. My problem now is that the average user doesnt understand the difference between "m" and "D". What I want to do is change this so that, as excel does, rather than showing the format string, I show how an arbitrary date would look like using that format.
The WPF combobox SelectedItem is bound to a dependency property in the date format picker clas, which the other control containing this date picker also binds to.
Example:
The user selects "January, 15" so the dependency property is set to "m".
Through code behind, the value of the dependency property is set to "D", the combobox is updated to display "Thursday, 15 January 1970" as the selected item.
I tried using converters but ConvertBack was impossible since I cant extract a format string used to create a certain date.

You could create a class DateFormatChoice that contains a property for the format code (e.g., "m" or "D") and a property for the current date formatted in that way.
public class DateFormatChoice {
public string FormatCode { get; private set; }
public string CurrentDateExample {
get { return DateTime.Now.ToString( FormatCode ) }
}
public DateFormatChoice( string standardcode ) {
FormatCode = standardcode;
}
}
You bind your ComboBox to a collection of these using CurrentDateExample in either your DataTemplate or as the ComboBox's DisplayMemberPath. You can either use these objects directly with your date format picker class and the DatePicker binds to the FormatCode property of the chosen DateFormatChoice object, or you can set the ValueMemberPath property on the original ComboBox to the FormatCode property and use SelectedValue on the ComboBox for getting/setting what is chosen. Not using ValueMember might be a little easier.
Here's a more full example. It uses the DateFormatChoice class above.
First, a data collection.
public class DateFormatChoices : List<DateFormatChoice> {
public DateFormatChoices() {
this.Add( new DateFormatChoice( "m" ) );
this.Add( new DateFormatChoice( "d" ) );
this.Add( new DateFormatChoice( "D" ) );
}
}
Then I made simple ViewModel for the Window:
public class ViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged = ( s, e ) => {
}; // the lambda ensures PropertyChanged is never null
public DateFormatChoices Choices {
get;
private set;
}
DateFormatChoice _chosen;
public DateFormatChoice Chosen {
get {
return _chosen;
}
set {
_chosen = value;
Notify( PropertyChanged, () => Chosen );
}
}
public DateTime CurrentDateTime {
get {
return DateTime.Now;
}
}
public ViewModel() {
Choices = new DateFormatChoices();
}
// expression used to avoid string literals
private void Notify<T>( PropertyChangedEventHandler handler, Expression<Func<T>> expression ) {
var memberexpression = expression.Body as MemberExpression;
handler( this, new PropertyChangedEventArgs( memberexpression.Member.Name ) );
}
}
I didn't have a date picker control that accepted the standard string format codes, so I made a quite dumb UserControl (with many corners cut) just to demonstrate its receipt of the format code. I gave it a dependency property called DateFormatProperty of type string and specified a value changed callback in the UIPropertyMetadata.
<Grid>
<TextBlock Name="datedisplayer" />
</Grid>
The callback:
private static void DateFormatChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e ) {
var uc = obj as UserControl1;
string code;
if ( null != ( code = e.NewValue as string ) ) {
uc.datedisplayer.Text = DateTime.Now.ToString( code );
}
}
And this is how I tied it all together in the Window.
<StackPanel>
<StackPanel.DataContext>
<local:ViewModel />
</StackPanel.DataContext>
<ComboBox
ItemsSource="{Binding Choices}" DisplayMemberPath="CurrentDateExample"
SelectedItem="{Binding Chosen, Mode=TwoWay}"/>
<local:UserControl1
DateFormatProperty="{Binding Chosen.FormatCode}" />
</StackPanel>

Related

Mode=TwoWay return Null

After a lot of hours I finally found what is the problem that cause the bug. Before to show the code that present the problem I need to explain the situation.
Binding and properties structure
In my application there is a ComboBox that bind as ItemSource a list of Rounds and as SelectedItem the Round selected from the list by the user.
The ComboBox have this structure:
<ComboBox ItemsSource="{Binding Rounds}" DisplayMemberPath="RoundName" SelectedItem="{Binding SelectedRound, Mode=TwoWay}" />
as you can see I've as modality TwoWay this allow me to update the property SelectedRound automatically when the user change the Item selected.
This is the class Round:
public class Round
{
public int Id { get; set; }
public string Link { get; set; }
public bool Selected { get; set; }
public string RoundName { get; set; }
}
and this is the properties used by the ComboBox:
//List of rounds available
private List<Round> _rounds;
public List<Round> Rounds
{
get { return _rounds; }
set
{
_rounds = value;
OnPropertyChanged();
}
}
//Selected round on ComboBox
private Round _selectedRound;
public Round SelectedRound
{
get { return _selectedRound; }
set
{
_selectedRound = value;
OnPropertyChanged();
}
}
both properties implement the OnPropertyChanged().
How the properties valorization works
In the app there is a method called LoadRounds() that is called each time the user press a button, this method have the following instruction:
public void LoadRounds(Team team)
{
//Fill the source of ComboBox with the rounds of the new team
Rounds = team.Rounds.ToList(); //<- Create a copy, so no reference
//Get the selected round
SelectedRound = Rounds?.FirstOrDefault(x => x.Id == team.CurrentRound.Id);
}
the SelectedRound is taken from a team property called CurrentRound, in particular each team have a round, so for a practice example:
[Rounds id available in Rounds property]
37487
38406
38405
37488
37486
...
[CurrentRound id of team]
38405
so the SelectedRound will contain the Round with Id 38405, and the linq query working well.
The problem
I set a breakpoint on _selectedRound = value;, the first firing time the value is a Round item (38405), but there is also a second firing time (that shouldn't be) that have as value null.
After a lot of hours spended on pc to understand why this situation happen I figure out.
Seems that the ComboBox (the TwoWay mode) doesn't know how to map the SelectedRound from the ItemSource, so essentially:
1. [Item Source updated with new Rounds]
2. [SelectedRound updated from the new `Rounds` available]
3. [SelectedRound setter called again with a null value]
I used also the stack call window for see if there is any method that call the setter property another time, but there is no external method that call the setter, so I guess is the TwoWay mode that fire the setter again.
How can I fix this situation? I know that this post is a bit complicated, I'm available to answer to all questions, and for provided more details if needed.
Thanks to all, have a good day.
UPDATE #1
This is my INotifyPropertyChanged implementation:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
UPDATE #2
The method LoadRounds is called when the user change the selection on a DataGrid, the DataGrid contains all teams, so I get the team selected by the user on the DataGrid, and then call the method LoadRounds.
All the teams are contained in a DataGrid, the ItemSource is a List<Team>.
At the end of the method LoadRounds I save the current Round of the Team on a property called SelectedRoundSaved, simply doing:
SelectedRoundSaved = Clone(SelectedRound);
in this way I prevent to reload the Rounds if the SelectedRoundSaved is equal to SelectedRound.
the Clone method allow me to clone the object, and have this structure:
public T Clone<T>(T source)
{
if (ReferenceEquals(source, null))
{
return default(T);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
it use the NewtonSoft.Json library.
This information isn't necessary at all, but as I said I'll add all info asked from you, thanks for the attention.
Are you sure this order is correct?
1. [Item Source updated with new Rounds]
2. [SelectedRound updated from the new `Rounds` available]
3. [SelectedRound setter called again with a null value]
After the combo box is initially bound I would expect the order to be (swapped the order of #2 and #3)
1. [Item Source updated with new Rounds]
2. [SelectedRound setter called again with a null value]
3. [SelectedRound updated from the new `Rounds` available]
This behavior follows what I would expect of a combo box.
When you update the ItemSource the ComboBox dumps its items and reloads with the new collection. Because the ComboBox is a Selector, it must then check its SelectedItem. If its SelectedItem is not found in the new collection it updates its SelectedItem to be null. All of this happens just because of the OnPropertyChanged(); call in the Rounds setter.
(Note: you will only see this behavior after a combo box has been loaded and bound)
Now there are many ways you can go about handling this, but IMO the simplest is merely to change the order of operations:
public void LoadRounds(Team team)
{
//Fill the source of ComboBox with the rounds of the new team
var newRounds = team.Rounds.ToList(); //<- Create a copy, so no reference
//Get the selected round
SelectedRound = newRounds.FirstOrDefault(x => x.Id == team.CurrentRound.Id);
Rounds = newRounds;
}

Set SelectedItem of ComboBox from object

I'm building an MVVM Light WPF app in Visual Studio 2015 with Entity Framework 6 (EF) providing the data. I have a ComboBox that displays the reasons why someone needs to take a drug test and it looks like this:
<ComboBox ItemsSource="{Binding ReasonsForTest}"
SelectedItem="{Binding Path=ReasonsForTestVm,
UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Description" />
The ReasonsForTest is of type ReasonForTestViewModel class:
public class ReasonForTestViewModel: ViewModelBase
{
private int _ReasonForTestId;
private string _ReasonForTestAbbr;
private string _description;
public int ReasonForTestId
{
get { return _ReasonForTestId; }
set
{
if (value == _ReasonForTestId) return;
_ReasonForTestId = value;
RaisePropertyChanged();
}
}
public string ReasonForTestAbbr
{
get { return _ReasonForTestAbbr; }
set
{
if (value == _ReasonForTestAbbr) return;
_ReasonForTestAbbr = value;
RaisePropertyChanged();
}
}
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
RaisePropertyChanged();
}
}
}
I have a data service class that contains the following code to fetch the data for the valid values of the ComboBox:
public async Task<ObservableCollection<ReasonForTestViewModel>> GetReasonsForTest()
{
using (var context = new MyEntities())
{
var query = new ObservableCollection<ReasonForTestViewModel>
(from rt in context.ReasonForTests
orderby rt.description
select new ReasonForTestViewModel
{
ReasonForTestId = rt.ReasonForTestID,
ReasonForTestAbbr = rt.ReasonForTestAbbr,
Description = rt.description,
});
return await Task.Run(() => query);
}
}
The view model populates the ComboBox using this:
var dataService = new TestDataService();
ReasonsForTest = await dataService.GetReasonsForTest();
The ComboBox has the correct data; however, it's not selecting the correct value when the app starts -- it's showing blank on load. The SelectedItem (ReasonsForTestVm) is also of that class type ReasonForTestViewModel and gets populated from the database with the one item for this person. I've stepped through the code to ensure ReasonsForTestVm has the correct data, and it does.
Here's the property for ReasonsForTestVm:
public ReasonForTestViewModel ReasonForTestVm
{
get
{
return _reasonForTestVm;
}
set
{
if (Equals(value, _reasonForTestVm)) return;
_reasonForTestVm = value;
RaisePropertyChanged();
}
}
What am I doing wrong here? I'm about to lose my mind!
Update: Sorry for the confusing name in the property above. Fixed.
Any WPF items control that extends Selector (such as ComboBox and ListBox) has two properties that are often used in conjunction: ItemsSource and SelectedItem.
When you bind a collection to ItemsSource, a representation of those items are shown in the UI. Each one of the representations is bound to an instance found within the collection bound to ItemsSource. If, for an example, you're using a DataTemplate to create that representation, you'll find within each that the DataContext will be one of those instances from the collection.
When you select one of these representations, the SelectedItemproperty now holds the instance from the collection that was bound to that representation.
This works perfectly through user interaction with the UI. However, there's one important caveat when interacting with these controls programmatically.
It's a very common pattern to bind these properties to similar properties in your view model.
public class MuhViewModel
{
public MuhItems[] MuhItems {get;} = new[]{ new Item(1), new Item(2) };
// I don't want to show INPC impls in my sample code, kthx
[SuperSlickImplementINotifyPropertyChangedAttribute]
public MuhSelectedItem {get;set;}
}
bound to
<ComboBox ItemsSource="{Binding MuhItems}"
SelectedItem="{Binding MuhSelectedItem}" />
If you try to manually update the selected item this way...
muhViewModel.MuhSelectedItem = new Item(2);
The UI will not change. The Selector sees that ItemsSource has changed, yes, but it doesn't find that instance in the ItemsSource collection. It doesn't know that one instance of Item with a value of 2 is equivalent to any other Item with the same value. So it does nothing. (That's a bit simplistic for what really happens. You can bust out JustDecompile and see for yourself. It gets real convoluted down in there.)
What you should be doing in this situation is updating SelectedItem with an instance found within the collection bound to ItemsSource. In our example,
var derp = muhViewModel.MuhItems.FirstOrDefault(x => x.MuhValue == 2);
muhViewModel.MuhSelectedItem = derp;
Side note, when tracking instances within a debug session, it helps to use Visual Studio's Make Object ID feature.

Changing the text field of a button on creation in xaml

I have an application which has support for multiple different languages and as such one of the requirements is that the user experience should not change regardless of language. I'm currently have a dialog box which has multiple buttons in English but is there a way to dynamically change the text fields of the buttons?
I would suggest a MarkupExtension that does all the heavy work:
public class TranlationExtension : MarkupExtension
{
public string Key { get; set; }
public TranlationExtension(string key)
{
this.Key = key;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
Binding binding = new Binding("TranslationDictionary[" + Key + "]");
binding.Source = MyTranslations;
return binding.ProvideValue(serviceProvider);
}
}
The MyTranslations must be a class that implements INotifyPropertyChanged if the Culture Changes.
Useage:
<Button Content="{local:Tranlation TheKeyOfTheTranslationThatIsUsedInTheDictionaryAswell }"/>
The Approach is described here http://www.wpftutorial.net/LocalizeMarkupExtension.html
You can bind content property of the button to property in your viewmodel, for example:
<Button Content="{Binding ButtonTxt}"/>
Then you can set ButtonTxt property in viewmodel regardles of selected language e.g.
if (languageId == Languages.English) {
ButtonTxt = buttonTextInEnglish;
}

How to Bind a List<object> to DataGrid using MVVM at Runtime

All, I have an View model that is bound to a DataGrid using MVVM.
<DataGrid ItemsSource="{Binding Path=Resources}">...</DataGrid>
Where
public ObservableCollection<ResourceViewModel> Resources { get; private set; }
in the ResourceViewModel class I have the following properties
public string ResourceName
{
get { return this.resource.ResourceName; }
set {
...
}
}
public ObservableCollection<string> ResourceStringList
{
get { return this.resource.ResourceStringList; }
set {
...
}
}
All properties are displayed in the DataGrid but the ResourceStringList colletion is being displayed as '(Collection)'.
How can I get the DataGrid to display each of the strings contained in the ResourceStringList in its own column?
Thanks very much for your time.
Edit. I have implemented the suggestion by #Marc below. I now have the following screenshot to illustrate what I now require:
The blank column before my resources column index 3 (zero indexed) is not required, how do I remove this column?.
I would also like to know how to add column names to my resource columns? Perhaps I can just add a Binding to Header property of the SeedColumn.
Again thanks for your time.
A datagrid is usually used to display a list of items of the same type with a fixed set of properties per item where each column is one property. So each row is one item, each column is one property on the item. You're case is different, as there is no fixed set of properties but a collection you want to show as if it were a fixed set of a number of properties.
The way to go greatly depends on whether you only want to display the data or whether you want to allow the user to manipulate the data. While the first can be achieved relatively easy using value converters, the latter requires a little more coding to extend the DataGrid class to allow for this behavior. The solutions I show are two of a thousand possibilities and probably not the most elegant ones. That being said, I will describe both ways and start with the two-way version.
TWO-WAY BINDING (ALLOWS EDITING)
The sample project (100KB)
I created a custom DataGrid and a custom 'DataGridColumn', called 'SeedColumn'. SeedColumn works just as a textcolumn, but has a property CollectionName. The DataGrid will add one new text column per item in the collection you've specified in CollectionName on the right hand side of the seed column. The seed column only works as a kind of placeholder to tell the DataGrid where to insert which columns. You could use multiple Seedcolumns in one grid.
The Grid and the column classes:
public class HorizontalGrid : DataGrid
{
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
foreach (var seed in Columns.OfType<SeedColumn>().ToList())
{
var seedColumnIndex = Columns.IndexOf(seed) + 1;
var collectionName = seed.CollectionName;
var headers = seed.Headers;
// Check if ItemsSource is IEnumerable<object>
var data = ItemsSource as IEnumerable<object>;
if (data == null) return;
// Copy to list to allow for multiple iterations
var dataList = data.ToList();
var collections = dataList.Select(d => GetCollection(collectionName, d));
var maxItems = collections.Max(c => c.Count());
for (var i = 0; i < maxItems; i++)
{
var header = GetHeader(headers, i);
var columnBinding = new Binding(string.Format("{0}[{1}]" , seed.CollectionName , i));
Columns.Insert(seedColumnIndex + i, new DataGridTextColumn {Binding = columnBinding, Header = header});
}
}
}
private static string GetHeader(IList<string> headerList, int index)
{
var listIndex = index % headerList.Count;
return headerList[listIndex];
}
private static IEnumerable<object> GetCollection(string collectionName, object collectionHolder)
{
// Reflect the property which holds the collection
var propertyInfo = collectionHolder.GetType().GetProperty(collectionName);
// Get the property value of the property on the collection holder
var propertyValue = propertyInfo.GetValue(collectionHolder, null);
// Cast the value
var collection = propertyValue as IEnumerable<object>;
return collection;
}
}
public class SeedColumn : DataGridTextColumn
{
public static readonly DependencyProperty CollectionNameProperty =
DependencyProperty.Register("CollectionName", typeof (string), typeof (SeedColumn), new PropertyMetadata(default(string)));
public static readonly DependencyProperty HeadersProperty =
DependencyProperty.Register("Headers", typeof (List<string>), typeof (SeedColumn), new PropertyMetadata(default(List<string>)));
public List<string> Headers
{
get { return (List<string>) GetValue(HeadersProperty); }
set { SetValue(HeadersProperty, value); }
}
public string CollectionName
{
get { return (string) GetValue(CollectionNameProperty); }
set { SetValue(CollectionNameProperty, value); }
}
public SeedColumn()
{
Headers = new List<string>();
}
}
The usage:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:loc="clr-namespace:WpfApplication1"
xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:sample="clr-namespace:Sample"
Title="MainWindow" Height="350" Width="525">
<Grid>
<sample:HorizontalGrid ItemsSource="{Binding Resources}" AutoGenerateColumns="False">
<sample:HorizontalGrid.Columns>
<sample:SeedColumn CollectionName="Strings" Binding="{Binding Name}" Header="Name" Visibility="Collapsed">
<sample:SeedColumn.Headers>
<system:String>Header1</system:String>
<system:String>Header2</system:String>
<system:String>Header3</system:String>
<system:String>Header4</system:String>
</sample:SeedColumn.Headers>
</sample:SeedColumn>
</sample:HorizontalGrid.Columns>
</sample:HorizontalGrid>
</Grid>
</Window>
and the ViewModels I've used for testing:
public class MainViewModel
{
public ObservableCollection<ResourceViewModel> Resources { get; private set; }
public MainViewModel()
{
Resources = new ObservableCollection<ResourceViewModel> {new ResourceViewModel(), new ResourceViewModel(), new ResourceViewModel()};
}
}
public class ResourceViewModel
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public ObservableCollection<string> Strings { get; private set; }
public ResourceViewModel()
{
Name = "Resource";
Strings = new ObservableCollection<string> {"s1", "s2", "s3"};
}
}
and the look (old version without headers):
ADDENDUM:
Regarding the new questions and your comment:
The NullReferenceException can have several reasons, but you've obviously
solved it. However, the line where it occured is a bit of spaghetti
code and I wouldn't do it like this in production code. You need to
handle the things that can go wrong in any case... I've modified the
code and refactored the line into its own method. This will give you
an idea of what's going on, when the exception is thrown.
The empty column that you see is the seed column, which is obviously not bound to anything. My idea was to use this column as a kind of row
header and bind it to the Name of the resource. If you don't need
the seedcolumn at all, just set its Visibility to collapsed.
<loc:SeedColumn CollectionName="Strings" Visibility="Collapsed">
Adding column headers is not difficult, but you need to think
about where you want to take the from. As you store all your strings
in a list, they are just strings, so not related to a second string
which you could use as a header. I've implemented a way to sepcify the
columns purely in XAML, which might be enough for you for now: You can
use it like this:
<loc:HorizontalGrid ItemsSource="{Binding Resources}" AutoGenerateColumns="False">
<loc:HorizontalGrid.Columns>
<loc:SeedColumn CollectionName="Strings" Binding="{Binding Name}" Header="Name" Visibility="Collapsed">
<loc:SeedColumn.Headers>
<system:String>Header1</system:String>
<system:String>Header2</system:String>
<system:String>Header3</system:String>
<system:String>Header4</system:String>
</loc:SeedColumn.Headers>
</loc:SeedColumn>
</loc:HorizontalGrid.Columns>
</loc:HorizontalGrid>
If you have more elements in the collection than headers specified,
the column headers will be repeated "Header3", "Header4", "Header1",..
The implementation is straight forward. Note that the Headers property
of the seed column is bindable as well, you can bind it to any List.
ONE-WAY BINDING (NO EDITING OF THE DATA)
A straight-forward way is to implement a converter which formats your data in a table and returns a view on this table to which the DataGrid can be bound. The disadvantage: It does not allow editing the strings, because once the table is created from the original data source, no logical connection between the displayed data and the original data exists. Still, changes on the collection are reflected in the UI, as WPF performs the conversion every time the data source changes. In short: This solution is perfectly fine if you only want to display the data.
How does it work
Create a custom value converter class, which implements IValueConverter
Create an instance of this class in your XAML resources and give it a name
Bind the grid's ItemsSource with this converter
This is how it would look like (my IDE is StackOverflow, so please check and correct, if necessary):
public class ResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var resources = value as IEnumerable<ResourceViewModel>;
if (resources== null) return null;
// Better play safe and serach for the max count of all items
var columns = resources[0].ResourceStringList.Count;
var t = new DataTable();
t.Columns.Add(new DataColumn("ResourceName"));
for (var c = 0; c < columns; c++)
{
// Will create headers "0", "1", "2", etc. for strings
t.Columns.Add(new DataColumn(c.ToString()));
}
foreach (var r in resources)
{
var newRow = t.NewRow();
newRow[0] = resources.ResourceName;
for (var c = 0; c < columns; c++)
{
newRow[c+1] = r.ResourceStringList[c];
}
t.Rows.Add(newRow);
}
return t.DefaultView;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then define a resource in your XAML like this, where loc is your namespace:
<loc:ResourceConverter x:Key="Converter" />
and then use it like this:
<DataGrid ItemsSource="{Binding Resources, Converter={StaticResource Converter}}" />
I don't think there is a out of the box solution for your problem and your grid columns will have to be created manually. In my case I do it when my DataGrid is loaded. I worked on assumption that number of columns is fixed for each element, 10 in my example, and that they are in correct order:
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
var dataGrid = sender as DataGrid;
dataGrid.Columns.Clear();
DataGridTextColumn resourceName = new DataGridTextColumn();
resourceName.Header = "Name";
resourceName.Binding = new Binding("ResourceName");
dataGrid.Columns.Add(resourceName);
for (int i = 0; i < 10; i++)
{
var resourceColumn = new DataGridTextColumn();
resourceColumn.Header = "Resource " + i;
resourceColumn.Binding = new Binding(String.Format("ResourceStringList[{0}]", i)) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
dataGrid.Columns.Add(resourceColumn);
}
}
here is some simple example on Dropbox

binding an editable combobox and detect inserted text in wpf

I have a ComboBox that it looks like this:
<ComboBox
ItemsSource="{Binding JobList}"
SelectedValue="{Binding Job,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
DisplayMemberPath="Title"
SelectedValuePath="Id"
IsEditable="True"
StaysOpenOnEdit="True"
/>
and its binding to my ViewModel that looks like this one:
public class ViewModel {
// this will fill from a database record for a person
public Job Job {
get { return _job; }
set {
if(value == _job) return;
_job = value;
OnPropertyChanged( () => Job );
}
}
// this will fill from all jobs records in database
public ObservableCollection<Job> JobList
{ /* do same as Job to implementing INotifyPropertyChanged */ }
}
and the Job is:
public class Job {
public int Id { get; set; }
public string Title { get; set; }
}
Really, I want to fill the ComboBox with job's list. So if the user's specified Job was in list, user can select it from list, otherwise, he enter a new Job.Title in ComboBox, the view model notify on it, and create a new Job item and also add it to JobList.
Have you any idea? can you help me please?
Create a string property in the viewModel something like 'SelectedJobName'
Bind this property to Combobox.Text
Wherever you want to use the entered value (Command, Presenter), check if selected value is not null and selectedJobName property value is not/matching.

Categories

Resources