I am setting ListView ItemSource to a List<T> where T is my Model. I am Binding some of the Property of this List<T> to some Label in XAML. And Now based on a Property, I want to Set Label to some Text.
For Example, if (Property.IsCompleted == true), I might want to set a Label in my View Cell in the ListView to "Done" instead of "True".
I hope this summarizes the problem. I have tried other things and none worked.
This is the Item Appearing Method of My ListView:
private void bookingLV_ItemAppearing(object sender, ItemVisibilityEventArgs e)
{
BookingsModel convert = (BookingsModel)e.Item;
var select = convert.IsCompleted;
if(select == true)
{
IsDone = "Completed";
}
IsDone = "Pending";
}
And I have a Custom Property called IsDone:
public string IsDone { get; set; }
And This is how I am Binding IsDone in the View Cell of the ListView in Xaml
<Label Text="{Binding IsDone}"></Label>
I want to be able to set the Text Property of my Label to some text based on a property of my Model Object.
create a read only property in your model that returns a value based on another property
public string IsDone
{
get
{
if (select) return "Completed";
return "Pending";
}
}
if you are using INotifyPropertyChanged you will want to be sure that the setter of the "trigger" property fires PropertyChanged events for both
public bool selected {
get {
...
}
set {
...
PropertyChanged("selected");
PropertyChanged("IsDone");
}
}
Related
I have a ComboBox that is bound to a property on my ViewModel (from hear on "VM".) When a user makes a selection on the ComboBox it properly updates the bound property in my VM. Within my UI code, I have subscribed to the PropertyChanged event on my VM.
As it should behave, when the user makes a selection within the ComboBox, my PropertyChanged event is correctly executing in my UI back-end code. When the UI code catches the change of this property, under certain selection conditions I need to halt the process and request the user for additional information. From the UI, I send them a dialog. If they cancel the dialog, I reset the value in the VM that is associated with the ComboBox controls SelectedValue.
This is what I've observed. When the operation is cancelled by the user, my VM property is being set to the new value. However, the ComboBox is still showing the text value of the original entry that has now changed. How can I force the ComboBox to update itself from within my PropertyChanged event? In this case, I think it's just a textual issue or numeric index change that's referencing the text data from the bound collection. The data is correct in the VM but the display value for the ComboBox is wrong.
EXAMPLE
ComboBox Details
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
Height="27" />
Sorry for the wordy properties on the VM, but that's to explain what's happening. My ListOfComboBoxDisplayObjects collection represents a set of enumerator values that are stored in the path within SelectedValuePath. The descriptive text for each value is pulled from the ListOfComboBoxDisplayObjects which is a special list strictly created for the UI. This basically pairs an enumeration value with a meaningful description.
ListOfComboBoxDisplayObjects Definition (from within VM)
Edit #1 - Added this definition to my example
private ObservableCollection<BindableEnumerationItem<Values>> _listOfComboBoxDisplayObjects;
public ObservableCollection<BindableEnumerationItem<Values>> ListOfComboBoxDisplayObjects
{
get { return _listOfComboBoxDisplayObjects; }
private set
{
if (value != _listOfComboBoxDisplayObjects)
{
_listOfComboBoxDisplayObjects= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ListOfComboBoxDisplayObjects)));
}
}
}
MySelectionIsAnEnumeration Definition (From within VM)
*Edit #1: Adding this code definition.
private Values_mySelectionIsAnEnumeration ;
public Values MySelectionIsAnEnumeration
{
get { return _mySelectionIsAnEnumeration; }
set
{
//Double-checked this-- value is different on the second-call to change this value, once the UI cancels the operation.
if (value != _mySelectionIsAnEnumeration)
{
_mySelectionIsAnEnumeration= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MySelectionIsAnEnumeration )));
}
}
}
Pertinent Values Associated with ListOfComboBoxDisplayObjects
These values are generated in the ctor of the VM. They are fixed throughout the application.
Item #1
Text: "This is a Foo!"
Value: Values.Foo
Item #2:
Text: "Hi, I'm Bar."
Value: Values.Bar
Item #3:
Text: "This is Baz. I need to ask a question before I can be used."
Value: Values.Baz
PropertyChanged Event - From the UI Back-End
private void VM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MySelectionIsAnEnumeration":
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if(!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
break;
}
}
After executing the above code, what I'm observing is that the VM.MySelectionIsAnEnumeration value is indeed changing to the proper value of Value.Foo when a user cancels the operation within AskAQuestionAndGetAResult(). However, after it's finished the ComboBox still reads "This is Baz. I need to ask a question before I can be used.", which is obviously the display value associated with Value.Baz.
How can I update both the underlying VM property AND the display text on the CombobBox to correctly show the valued that is now stored in VM.MySelectionIsAnEnumeration?
Edit #2
Below is the code efor my BindableEnumerationItem that I use within my Observable Collections for comboxes and list boxes. This is used throughout my application in simpler cases and has caused no issue. Please note, this is my actual, unaltered code. I've not renamed anything. My comboboxes can bind to each Item property for a type-safe property and DisplayText is the descriptor text.
public class BindableEnumerationItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private T _item;
public BindableEnumerationItem(T item, string displayText)
{
_item = item;
_displayText = displayText;
}
private string _displayText;
public string DisplayText
{
get { return _displayText; }
set
{
if (value != _displayText)
{
_displayText = value;
PropertyChanged(this, new PropertyChangedEventArgs("DisplayText"));
}
}
}
public T Item
{
get { return _item; }
set
{
_item = value;
PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
}
create an extension that will wire up the command from your viewmodel in xaml to the selector, which in this case is the combobox.
public partial class Extensions
{
public static readonly DependencyProperty SelectionChangedCommandProperty = DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Selector;
if (element != null)
{
element.SelectionChanged -= OnSelectionChanged;
if (e.NewValue != null)
{
element.SelectionChanged += OnSelectionChanged;
}
}
}));
public static ICommand GetSelectionChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(SelectionChangedCommandProperty);
}
public static void SetSelectionChangedCommand(UIElement element, ICommand value)
{
element.SetValue(SelectionChangedCommandProperty, value);
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var element = sender as Selector;
var command = element.GetValue(SelectionChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element.SelectedItem))
{
command.Execute(element.SelectedItem);
e.Handled = true;
}
}
}
Create the command in the viewmodel that handles the value changed event.
public ICommand EnumerationValueChangedCommand
{
get
{
return new Command(
() =>
{
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if (!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
});
}
}
And then bind using that extension. ext is the namespace for your extensions.
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
ext:Extensions.SelectionChangedCommand="{Binding EnumerationValueChangedCommand}"
Height="27" />
I have checkbox in my view which has bound to the property in the viewmodel. When I check/uncheck the checkbox, there is one condition in setter of the property which updates the same property if the condition is true. But when the property gets updated corresponding view does not change.
Here is the code:
View:
<CheckBox IsChecked="{Binding HoldingPen,Mode="Twoway" ,UpdateSourceTrigger=PropertyChanged}"/>
ViewModel:
public bool HoldingPen
{
get{m_holdingPen;}
set
{
m_hodingPen=value;
onPropertyChanged("HoldingPen");
OnHoldingPenCheckChanged();
}
public void OnHoldingPenCheckChanged()
{
if(HoldingPen && some other condition)
{
HoldingPen=false; //Here view should be updated simultaneously
}
}
I think it's a result of having two onPropertyChanged events fire, once with a value of true and one with a value of false
Typically for this kind of logic I prefer to use the PropertyChanged event instead of hiding the logic in property setters.
public class MyClass()
{
public MyClass()
{
// attach property changed in constructor
this.PropertyChanged += MyClass_PropertyChanged;
}
private void MyClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "HoldingPen")
OnHoldingPenCheckChanged();
}
public bool HoldingPen
{
get{ m_holdingPen; }
set
{
if (m_hodingPen == value)
return;
m_hodingPen=value;
onPropertyChanged("HoldingPen");
}
}
public void OnHoldingPenCheckChanged()
{
if(HoldingPen && some other condition)
{
HoldingPen=false; //Here view should be updated simultaneously
}
}
}
This has the additional benefit of having any custom code to modify a value in one location, rather than going through each setter when looking for something.
I have a ListBox whose ItemTemplate looks like this:
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"/>
</DataTemplate>
Column is a simple class which looks like this:
public Column(string name, bool isVisibleInTable)
{
Name = name;
IsVisibleInTable = isVisibleInTable;
}
public string Name { get; set; }
public bool IsVisibleInTable { get; set; }
The EditableTextBlock is a UserControl that turns into a TextBox when double clicked and turns back into a TextBlock when Lost Focus. It also has a Property called IsInEditMode which is by default false. When it is true, TextBox is shown.
The Question:
The ItemsSouce of the ListBox is an ObservableCollection<Column>. I have a button which adds new Columns to the collection. But my problem is that I want IsInEditMode to be turned true for newly added EditableTextBlocks by that Button. But I can only access Column in the ViewModel. How will I access the EditableTextBlock of the specified Column in the ItemsSource collection?
The only solution I can come up with is deriving a class from Column and adding a property for that (eg: name: IsInEditMode) (Or maybe a wrapper class. Here's a similar answer which suggestes using a wrapper class) and Binding to that property in the DataTemplate like so:
<DataTemplate DataType="local:DerivedColumn">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"
IsInEditMode="{Binding IsInEditMode}"/>
</DataTemplate>
But I don't want this. I want some way to do this in XAML without deriving classes and adding unnecessary code. (And also adhering to MVVM rules)
If you have scope to add a new dependency property to the EditableTextBlock user control you could consider adding one that has the name StartupInEditMode, defaulting to false to keep the existing behavior.
The Loaded handler for the UserControl could then determine the status of StartupInEditMode to decide how to initially set the value of IsInEditMode.
//..... Added to EditableTextBlock user control
public bool StartupInEdit
{
get { return (bool)GetValue(StartupInEditProperty); }
set { SetValue(StartupInEditProperty, value); }
}
public static readonly DependencyProperty StartupInEditProperty =
DependencyProperty.Register("StartupInEdit", typeof(bool), typeof(EditableTextBlock ), new PropertyMetadata(false));
private void EditableTextBlock_OnLoaded(object sender, RoutedEventArgs e)
{
IsInEditMode = StartupInEditMode;
}
For controls already in the visual tree the changing value of StartupInEdit does not matter as it is only evaluated once on creation. This means you can populate the collection of the ListBox where each EditableTextBlock is not in edit mode, then swap the StartupInEditmMode mode to True when you start adding new items. Then each new EditableTextBlock control starts in the edit mode.
You could accomplish this switch in behavior by specifying a DataTemplate where the Binding of this new property points to a variable of the view and not the collection items.
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock"
Text="{Binding Name, Mode=TwoWay}"
StartupInEditMode="{Binding ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</DataTemplate>
You need to add a property to the parent Window (or Page or whatever is used as the containter for the view) called ANewViewProperty in this example. This value could be part of your view model if you alter the binding to {Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}.
This new property (ANewViewProperty) does not even need to implement INotifyPropertyChanged as the binding will get the initial value as it is creating the new EditableTextBlock control and if the value changes later it has no impact anyway.
You would set the value of ANewViewProperty to False as you load up the ListBox ItemSource initially. When you press the button to add a new item to the list set the value of ANewViewProperty to True meaning the control that will now be created starting up in edit mode.
Update: The C#-only, View-only alternative
The code-only, view-only alternative (similar to user2946329's answer)is to hook to the ListBox.ItemContainerGenerator.ItemsChanged handler that will trigger when a new item is added. Once triggered and you are now acting on new items (via Boolean DetectingNewItems) which finds the first descendant EditableTextBlock control for the appropriate ListBoxItem visual container for the item newly added. Once you have a reference for the control, alter the IsInEditMode property.
//.... View/Window Class
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
MyListBox.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
}
private void ItemContainerGenerator_ItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
{
if ((e.Action == NotifyCollectionChangedAction.Add) && DetectingNewItems)
{
var listboxitem = LB.ItemContainerGenerator.ContainerFromIndex(e.Position.Index + 1) as ListBoxItem;
var editControl = FindFirstDescendantChildOf<EditableTextBlock>(listboxitem);
if (editcontrol != null) editcontrol.IsInEditMode = true;
}
}
public static T FindFirstDescendantChildOf<T>(DependencyObject dpObj) where T : DependencyObject
{
if (dpObj == null) return null;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dpObj); i++)
{
var child = VisualTreeHelper.GetChild(dpObj, i);
if (child is T) return (T)child;
var obj = FindFirstChildOf<T>(child);
if (obj != null) return obj;
}
return null;
}
Update #2 (based on comments)
Add a property to the view that refers back to the the ViewModel assuming you keep a reference to the View Model in the DataContext:-
..... // Add this to the Window/Page
public bool DetectingNewItems
{
get
{
var vm = DataContext as MyViewModel;
if (vm != null)
return vm.MyPropertyOnVM;
return false;
}
}
.....
To get an element inside a template and change it's properties in code you need FrameworkTemplate.FindName Method (String, FrameworkElement) :
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Then:
for (int i = 0; i < yourListBox.Items.Count; i++)
{
ListBoxItem yourListBoxItem = (ListBoxItem)(yourListBox.ItemContainerGenerator.ContainerFromIndex(i));
ContentPresenter contentPresenter = FindVisualChild<ContentPresenter>(yourListBoxItem);
DataTemplate myDataTemplate = contentPresenter.ContentTemplate;
EditableTextBlock editable = (EditableTextBlock) myDataTemplate.FindName("editableTextBlock", contentPresenter);
//Do stuff with EditableTextBlock
editable.IsInEditMode = true;
}
A have a list box with check boxes (I removed the part about alignment, width, margin as not related to the case):
<ListBox
ItemsSource ="{Binding SItemCollection}"
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Item.Code}" IsChecked="{Binding IsChecked}"/>
</DataTemplate>
</ListBox.ItemTemplate>
I have a class SItem inside my ViewModel, which stores two fields - CachedStr which I get from Cache and a Boolean IsChecked which represents whether the item is checked or not (CachedStr object also has several fields (Name, Code etc), I've chosen to show the Code):
public class SItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CachedStr Item { get; set; }
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
NotifyPropertyChanged("IsChecked");
}
}
protected void NotifyPropertyChanged(string strPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
A have a Collection of SItems (SItemCollection), which fills my ListBox with items, some of which are ticked. This collection is outside the SItem class, it is inside my view model. I also have a set of all items (AvailableSItems) that should be available in the ListBox and a set of items that should be checked (ItemsToBeTicked) at the very beginning. This two sets contain objects of type CachedStr. By using those sets I get my SItemCollection:
public ObservableCollectionEx<SItem> SItemCollection
{
get
{
ObservableCollectionEx<SItem> strItems = new ObservableCollectionEx<SItem>();
this.AvailableSItems.ForEach(p =>
{
SItem item = new SItem();
item.Item = p;
item.IsChecked = false;
strItems.Add(item);
});
strItems.ForEach(p =>
{
if (this.ItemsToBeTicked.Contains(p.Item))
{
p.IsChecked = true;
}
else p.IsChecked = false;
}
);
return strItems;
}
}
The above-mentioned code works. But I also need a method which will get the final set of all ticked items (after, for example, pressing the button), and that's where I'm stuck. I do get a notifications when I tick or untick something.
The code currently creates a new instance of the collection in the get block. This has to be changed, otherwise the changes done in the UI will be reverted each time the get block is called.
Take the code which is currently in the get block, extract it to a method and use the return value of the method to set your SItemCollection property.
In constructor for example:
SItemCollection = CreateInitialCollection();
And the property will be simplyfied to:
public ObservableCollectionEx<SItem> SItemCollection
{
get
{
return _sitemCollection;
}
set
{
if (_sitemCollection!= value)
{
_sitemCollection= value;
RaisePropertyChanged("SItemCollection");
}
}
}
ObservableCollectionEx<SItem> _sitemCollection;
When this is fixed (and if the binding to the IsChecked property in SItem works), you can use a Linq expression:
var checkedItems = SItemCollection.Where(item => item.IsChecked == true)
My problem/situation is very similar to Wpf DataGrid SelectedItem loses binding after cell edit but I'm not using any "custom" WPF framework. I have a model that implements INotifyPropertyChanged and IEditableObject, and a grid bound to an ObservableCollection<T>. The grid's SelectedItem property is bound to a property on the VM.
With a break point, I can see my ViewModel.SelectedItem property change as I select different rows in the grid. The moment I change a value on a row, however, the ViewModel.SelectedItem property is no longer set as I change focus on the rows. The solution identified in the above link does not work since I'm not using a custom WPF framework, just naked WPF.
Any ideas?
// View model area
public IPurchaseorderItem SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
_selectedItem = value;
SelectItemCommand.NotifyCanExecuteChanged();
RemoveItemCommand.NotifyCanExecuteChanged();
}
}
}
// XAML SelectedItem binding
<views:NoBindingGroupDataGrid SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
// Special Grid to clear binding groups (read on a similarly themed SO question/answer)
internal sealed class NoBindingGroupDataGrid : DataGrid
{
private bool _editing = false;
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
var desiredSize = base.MeasureOverride(availableSize);
ClearBindingGroup();
return desiredSize;
}
protected override void OnCellEditEnding(DataGridCellEditEndingEventArgs e)
{
base.OnCellEditEnding(e);
if (!_editing)
{
_editing = true;
CommitEdit(DataGridEditingUnit.Row, true);
_editing = false;
}
}
private void ClearBindingGroup()
{
ItemBindingGroup = null;
foreach (var item in Items)
{
var row = ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
if (row != null)
{
row.BindingGroup = null;
}
}
}
}
Apparently the SelectedItem dependency property on DataGrid is broken and not being used correctly. After some debugging using OnPropertyChanged, I found that the grid is actually setting a "CurrentItem" property reliably. I changed to use CurrentItem instead and everything appears to work correctly... the user's "selected row" is being sent to the VM without incident.