Updating IValueConverter through code - wpf

I'm trying to figure out if its possible to update an IValueConverter through the code behind.
My situation is that I've got two ComboBoxes. Once the first one is updated, I change the ItemsSource property of the second to be one of a variety of enums. I've grabbed an EnumToFriendlyNameConverter from CodeProject, but I'm not sure how to set it.
If I set the converter in the ItemsSource (see below) then it gets ignored when I next set the items source.
ItemsSource="{Binding Converter={StaticResource enumItemsConverter}}"
I found that it is possible by using an ItemTemplate but then I have to manually place in a label, which then has a different style to my other combobox. Getting the styles right just seems like a lot of work...

When you change the ItemsSource you just have to apply the converter again or modify the ItemsSource instead of replacing it.
e.g. create a new binding:
private void ChangeItemsSouce(IEnumerable newItems)
{
Binding binding = new Binding();
binding.Source = newItems;
binding.Converter = new EnumToFriendlyNameConverter();
comboBox.SetBinding(ComboBox.ItemsSourceProperty, binding);
}
Or modify the existing binding:
private void ChangeItemsSouce(IEnumerable newItems)
{
var binding = comboBox.GetBindingExpression(ComboBox.ItemsSourceProperty);
binding.ParentBinding.Source = newItems;
}

Related

Rebinding Observable collection wpf

I created a WPF window to bind data in Datagrid based on the selection of date by the user.BY default it loads for a specific date which works fine. But When the date is changed, the grid is showing empty rows.but the observable collection I used is having data. The observable collection is of DataTable type.
Note: I used to set itemsource=null when there are no records since I am using the same grid for 2 different tables based on a radio button check.
I have set the public variable as Binding variable in the XAML, used INOtifyChanged interface.
My issues is when the same collection reloads, the Datagrid failed to bind and shows empty rows but generating columns. When the previous attempt set the itemsource as null, the current loading failed to load the column also.
Any generic scenario, I am facing,? Please help
My Code:
private ObservableCollection<DataTable> custInfoCol = new ObservableCollection<DataTable>();
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private DataTable _CustInfo;
public DataTable CustInfo
{
get { return _CustInfo; }
set
{
_CustInfo = value;
PropertyChanged(this, new PropertyChangedEventArgs("CustInfo"));
}
}
private void rbPrev_Checked(object sender, RoutedEventArgs e)
{
custInfoCol.Clear();
custInfoCol.Add(CustInfo = showcustomer(cid));
}
Sounds like you might need to change the UpdateSourceTrigger in your binding:
Height="{Binding Height, ElementName=Day, UpdateSourceTrigger=PropertyChanged}"
I've had problems with the default behavior of it before, so now I just type it out every time.
https://msdn.microsoft.com/en-us/library/system.windows.data.binding.updatesourcetrigger%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
Based on this piece of code you provided in the comment:
<DataGrid ItemsSource="{Binding custInfo,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
I think there is typo in the Xaml. You should use ... Binding CustInfo... with capital C.
Of course, if AutoGenerateColumns is true, then I guess this is not the problem, because you wrote
Datagrid failed to bind and shows empty rows but generating columns
Which the Binding works. Therefore, you need to check showcustomer(cid) and make sure it contains the required DataRows.
I fixed the issue which is very simple. I had created the datatable instance as global and i changed it to local just before loading the executereader() method.It resolved the issue.

WPF MVVM ListBox/ComboBox ItemsSource won't update UI from ViewModel

i have a container such as a listbox, combobox etc that its ItemsSource property is bound to an observable collection in my view model.
When i'm trying to add/remove items from the collection via some method in my VM it won't reflect in the UI,
The only way the UI would actually refresh is if i assign the collection a new value (i.e another collection with the relevant data) which forces him to re-bind the whole collection.
maybe i'm missing/don't understand something about the collection binding issue, either way if someone has a solution/good explanation/both it would be great.
here is a sample from my View(in this case its a listbox)
<ListBox
Grid.Row="9"
Grid.Column="1"
Grid.ColumnSpan="3"
Width="200"
Height="200"
ItemsSource="{Binding PreSavedRecordingScheduleList,UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedPreSavedRecordingSchedule,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Display"/>
and here is my ViewModel:
private ObservableCollection<ScheduledRecordingObject> m_PreSavedRecordingScheduleList;
PreSavedRecordingScheduleList = new ObservableCollection<ScheduledRecordingObject>();
public ObservableCollection<ScheduledRecordingObject> PreSavedRecordingScheduleList
{
get
{
return m_PreSavedRecordingScheduleList;
}
set
{
m_PreSavedRecordingScheduleList = value;
OnPropertyChanged("PreSavedRecordingScheduleList");
}
}
ScheduledRecordingObject also implements INotifyPropertyChanged.
viewmodel
public ObservableCollection<yourType> MyItemsSource {get;set}
initialize once in contructor and use clear, add and remove to alter it
view
<ListBox ItemsSource="{Binding MyItemsSource}"/>
just be sure that the right DataContext is set.
thats how it should look in your code
EDIT:
some hints to your posted code:
//remove the UpdateSourceTrigger=PropertyChanged - makes no sense the Mode is OneWay anyway :)
ItemsSource="{Binding PreSavedRecordingScheduleList}"
//the following line should just called once and at best in ctor
//but the binding will of course work too when you assign a new collection
PreSavedRecordingScheduleList = new ObservableCollection<ScheduledRecordingObject>();
all in all your code looks good, and if the viewmodel is the Datacontext of your Listbox then it should work. let me know what Snoop is showing :)
Remove the OnPropertyChanged("PreSavedRecordingScheduleList"); from the ObservableCollection. Actually you don't need a backing field. Attach the CollectionChanged event on the ObservableCollection, something like this
1- Inside the ViewModel constructor attach the event CollectionChanged
PreSavedRecordingScheduleList = new ObservableCollection<ScheduledRecordingObject>();
PreSavedRecordingScheduleList.CollectionChanged += PreSavedRecordingScheduleList_CollectionChanged;
2- Inject the OnPropertyChanged("PreSavedRecordingScheduleList") in the event handler
void PreSavedRecordingScheduleList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("PreSavedRecordingScheduleList");
}

Binding to a value of a string property

Is there anyway to bind to the value of a property, not property itself.
For example: There is a generic grid with two columns "Name" and "Code"
In viewmodel:
NameMemberPath = "Title"; // fetch from database
CodeMemberPath = "ProductCode"; // fetch from database
In Xaml:
<telerik:GridViewDataColumn UniqueName="Name" DataMemberBinding="{Binding Path=NameMemberPath}" />
<telerik:GridViewDataColumn UniqueName="Code" DataMemberBinding="{Binding Path=CodeMemberPath}" />
How to bind the Name to "Title" insted of NameMemberPath
It's possible to do it programmatically but I need to do it declaratively!
i would suggest to change your concept because the ViewModel should provides the Properties for the View directly
let's suggest you want a generic View which will bind to a Property MyAnyProb so it is now to problem of your ViewModel to present this Property.
your ViewModel does now his job by providing this property
public object MyAnyProb
{
get { return getMagic(); }
set { setMagic(value); }
}
// and her is the part where you need to add your logic
private object getMagic()
{
// let magic happen
}
private void setMagic(object probValue)
{
// other magic
}
now you are able to use your view generic with no worries about the property because your generic ViewModel will take care about it
for example:
var MyStrings = new List<string>();
MyStrings = MyFunctionToRetrieveAListOfStrings();
foreach(var str in MyStrings)
{
Binding binding = new Binding("YourControlValue");
binding.Source = str;
YourControl.SetBinding(TextBlock.TextProperty, binding)
}
http://msdn.microsoft.com/en-us/library/ms742863.aspx
IMHO you cannot do that in xaml. How about adding columns to your telerik grid from code behind? I am not very sure if this solution is feasible to you or not but worth a try.
GridViewDataColumn column = new GridViewDataColumn();
column.DataMemberBinding = new Binding(CodeMemberPath.ToString());
this.yourTelerikGrid.Columns.Add(column);
Hope this helps.
The key is to use the "SetBinding" method.
TextBlock myTextBlock = new TextBlock();
myTextBlock.SetBinding(TextBlock.TextProperty, new Binding("SomePropertyName"));
That should do the trick
No you always bind a DependencyProperty to a (public) Property.
(source: microsoft.com)
Read more about Binding in the MSDN!

Databind Combobox in WPF

I'm trying to databind a combobox in WPF for the first time and I can't get it to happen.
The image below shows my code, can you please tell me what I am missing? I only want graphic stuff in the xaml.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Patient p = new Patient();
this.cbPatient.DataContext = p.SelfListAll();
this.cbPatient.DisplayMemberPath = "Name";
this.cbPatient.SelectedValuePath = "PatientIDInternal";
}
...
Short explanation: Just make the following change to your XAML:
<ComboBox ItemsSource="{Binding Path=patientList}" />
Then, in your Window_Loaded event handler, just add
this.DataContext = this
Then make a new member called patientList of type ObservableCollection<Patient>.
Long explanation:
You don't have a binding set up. You need to create one through XAML like this:
<ComboBox ItemsSource="{Binding Path=patientList}" />
Then, the combobox will look for a member or property called "patientList" on the object that is set as its DataContext. I'd recommend using an ObservableCollection for patientList.
Alternatively, to create one in code, you can follow the examples here:
http://msdn.microsoft.com/en-us/library/ms752347.aspx#specifying_the_binding_source
Binding myBinding = new Binding("patientList");
myBinding.DataContext = someObject; //whatever object has 'patientList' as a member
mycombobox.SetBinding(ComboBox.ItemsSourceProperty, myBinding);
This will set a binding on the mycombobox ComboBox with a path of patientList and a DataContext of someObject. In other words, mycombobox will show the contents of someObject.patientList (which would ideally be some ObservableCollection, so that updates to the collection notify the binding to update).
You need to actually add the binding, e.g.:
Binding binding = new Binding();
binding.Source = MySourceObject;
binding.Path = new PropertyPath("MyPropertyPath");
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(cbPatient, SomeDependencyProperty, binding);
Ok, here is the answer to how to populate a combobox in WPF. First, thanks to everyone above who made suggestions. The part I was missing was that I was not populating the ItemsSource property but the DataContext property. Again, thanks to everyone for their help.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Patient p = new Patient();
this.cbPatient.ItemsSource = p.SelfListAll();
this.cbPatient.DisplayMemberPath = "Name";
this.cbPatient.SelectedValuePath = "PatientIDInternal";
this.cbPatient.SelectedIndex = 0;
}
You need to set the ItemsSource property relative to the DataContext:
cbPatient.SetBinding(ItemsSourceProperty, new Binding());
EDIT
The ItemsSource property of the ComboBox is the property that should point to the collection of items to be shown.
The collection you are interested in, is in the DataContext.
The Binding is an object that keeps track of changes of the collection and reports them to the ComboBox and its Path is relative to the object in the DataContext.
Because the Binding also needs to know the ComboBox you use the static SetBinding method that ties the connection between ComboBox and the Binding.
As in your code the collection itself is in the DataContext, the Path is empty.
The ItemsSource property should point to the collection of Patients. Because the collection of Patients is already in the DataContext, the Binding's Path property is empty.
Suppose an class named Hospital has two properties: Patients and Docters (and perhaps more: Rooms, Appointments, ...) and you set the DataContext of the ComboBox to an instance of Hospital. Then you would have to set the Binding's Path Property to "Patients"
Now the ComboBox will display each item (Patient) in the collection. To specify how a single Patient should be displayed you need to set the ItemTemplate property of the ComboBox.

DataGridColumn Binding in code

Does anyone know how I can do the equivalent XAML binding in code?
<DataGrid ... >
<DataGrid.Columns>
<DataGridTextColumn
Binding="{Binding Description}" <=== set in code **
/>
</DataGrid.Columns>
</DataGrid>
Cheers,
Berryl
=== UPDATE ====
It looks like the method I have been looking for is DataGridColumn.GenerateElement
If so, then the focus of this question is now how to set the Binding correctly. The reason I want to do this code is that my grid has 7 columns that are identical visually and whose data can be known by an index.
So I want to be able to simplify the xaml by using a subclass DataGridTextColumn which has an index property, and just have:
<DataGrid ... >
<DataGrid.Columns >
<local:DayOfWeekColumn Index="0" />
<local:DayOfWeekColumn Index="1" />
....
<local:DayOfWeekColumn Index="7" />
</DataGrid.Columns >
</DataGrid >
=== REVISED QUESTION ===
Assuming the Binding itself is logically and syntactically correct, what should the parameters to BindingOperations.SetBinding be??
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
var activity = (ActivityViewModel)dataItem;
var cellData = activity.Allocations[Index];
var b = new Binding
{
Source = cellData,
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
Converter = new AllocationAmountConverter()
};
BindingOperations.SetBinding(??, ??, b);
return ??;
}
=== EDITS for ARAN =====
I am not overriding GenerateElement right now, but rather trying to get a static helper to set my binding for me. The helper is needed in any event to compensate for not being able to bind Header content in the current implementation of MSFT's DataGrid.
Basically the idea is to catch the DC from the grid and use it as necessary on each of the columns, which in this case would be the Header content, cell style, and Binding. Here is the code:
public class TimesheetDataGridColumnContextHelper
{
static TimesheetDataGridColumnContextHelper() {
FrameworkElement.DataContextProperty.AddOwner(typeof (DataGridTextColumn));
FrameworkElement.DataContextProperty.OverrideMetadata(
typeof (DataGrid),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnDataContextChanged));
}
public static void OnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as DataGrid;
if (grid == null || !grid.Name.Equals("adminActivityGrid")) return;
foreach (var col in grid.Columns) {
var dowCol = col as DayOfTheWeekColumn;
if (dowCol == null) continue;
var context = (IActivityCollectionViewModelBase) e.NewValue;
var index = Convert.ToInt32(dowCol.DowIndex);
_setHeader(dowCol, context, index);
var editStyle = (Style) grid.FindResource("GridCellDataEntryStyle");
dowCol.CellStyle = editStyle;
_setBinding(dowCol, index, context);
}
}
private static void _setBinding(DayOfTheWeekColumn dowCol, int index, IActivityCollectionViewModelBase context) {
dowCol.Binding = new Binding
{
Path = new PropertyPath(string.Format("Allocations[{0}]", index)),
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
Converter = new AllocationAmountConverter()
};
}
private static void _setHeader(DataGridColumn col, IActivityCollectionViewModelBase context, int index)
{
var date = context.HeaderDates[index];
var tb = new TextBlock
{
Text = date.ToString(Strings.ShortDayOfWeekFormat),
ToolTip = date.ToLongDateString()
};
col.Header = tb;
}
}
}
Everything works except for the Binding. I can't tell if it's because my binding is wrong somehow (although I get no obvious errors) or this is not a good place to set it. The grid columns are just empty when I run it.
Any idea??
Cheers,
Berryl
=== FIXED! ===
The logic in the last update was actually correct, but getting lost in the internals of the DataGrid I missed that my Binding.Path was missing the property to be bound to! Credit to Aran for understanding the issue, realizing that GenerateElement overrides were not necessary, and catching that the Binding Source should not have been set.
You're always doing the fiddly grid bits eh Beryl?
Do a couple of things. Use reflector to look at the implementation of GenerateElement in the DataGridTextColumn. (.NET programmers live in reflector)
Now for the answer:
In the datagrid each column is not part of the visual tree. The column has two methods GenerateElement and GenerateEditingElement. These methods return the viewer and the editor for the cell respectively. In your method above you are not creating the viewer, which will probably be a TextBlock.
from reflector, the implementation of GenerateElement is as below, notice the first thing they do is create the viewer for the cell.
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
TextBlock e = new TextBlock();
this.SyncProperties(e);
base.ApplyStyle(false, false, e);
base.ApplyBinding(e, TextBlock.TextProperty);
return e;
}
Once you have a textblock you can use the line below to set the binding on it.
BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, binding);
I am not however convinced that you actually need to override the GenerateElement and GenerateEditingElement to get your desired effect. I think you could overide the Binding property of the base class and just modify the binding there with your extra field whenever it is set. This will mean everything else will just work and you wont end up removing functionality from your column. Once again a crawl through reflector looking at the class DataGridBoundColumn (the abstract base class) would be beneficial.
I do something similiar in one of our columns whenever a binding is set I modify the clipboard binding by adding an extra property so I can copy and paste effectively.
EDIT: Update...this should probably be another question but..
You are explicitly setting the source of the binding in your setBinding method. In the grid the source of the binding is the data contained in the row. You are setting it, which means it would be the same for each row. You can apply these funky bindings without the source property before the data context is set, the source becomes the item in each row, and your binding should reflect an index into the property held in each row.
Based on MSDN, it sounds like the first parameter of SetBinding() should be the control that you want to display the binding in (this in this case, assuming that GenerateElement() is a member of the DayOfWeekColumn class), and the second property is the property to bind the data to. I haven't used the WPF DataGrid very much, but I didn't see anything like a text property to set.
I do see that the DataGridTextColumn does have a Binding property, though. Maybe it would work to set it to the binding you created manually above?

Resources