Is it possible for a control binding to behave as OneWayToSource and OneTime?
Here is some background: I have a data grid. Each row has text cells and a checkbox. If the checkbox is selected the data from the row will be saved. Now, when user starts typing in any text cell I change the IsChecked property in my ViewModel so the checkbox gets checked. I would like this to happen only once. So, for example, if a user starts typing and decides to uncheck the checkbox I don't want to change it when the user will start typing value again.
Sounds for me like setting binding to both OneWayToSource and OneTime should be a solution but I know binding mode can be set only to a single value. I've been searching for some suggestions and possible workarounds to achieve similar result but with no result.
From msdn:
OneTime updates the target property only when the application starts
or when the DataContext undergoes a change
That means that using a combination of OneWayToSource and OneTime would not solve your issue as the 'one time'-update does not trigger the moment the property changes the first time but on application start or datacontext change.
As you bound your text cell text to some property you can control in that property if IsChecked should be set or not.
private string text_ = "";
private bool isChecked_ = false;
private bool autoSetChecked_ = true;
public bool IsChecked
{
get
{
return isChecked_;
}
set
{
if (isChecked_ == value) {
return;
}
// If user manually changes check state assume the user wants to keep that state
// => Disable auto changing.
autoSetChecked_ = false;
isChecked_ = value;
OnPropertyChanged("IsChecked");
}
}
public Text
{
get
{
return text_;
}
set
{
if (text_ == value) {
return;
}
text_ = value;
OnPropertyChanged("Text");
if (autoSetChecked_) {
// Only set is checked if not done ever before.
autoSetChecked_ = false;
IsChecked = true;
}
}
}
Edit: This requires that your IsChecked-Binding is TwoWay so you can change the checkbox from your viewmodel.
Related
Ok, so I have a datagrid with a checkbox in a DataGridTemplateColumn since I would like to do a single-click check. I also have a "select all" checkbox at the top. (I disabled the header because I wanted to make a custom one). My datagrid is programmatically populated. and I'm using the mahApps nuget package (if that means anything).
What I want to do is bind the "Select All" status to the IsChecked property on my populated checkboxes but also be able to click the row to check the checkbox. But to be able to do that, I need to add: IsChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Path=IsSelected, Mode=OneWay}".
I have both of them, and they both work, but I can only seem to use one at a time. How would I go about doing both? Please let me know if you need more info
Edit: I would also need multi-selection!
This is psuedo code:
To make each rows checkbox clickable independently - the ViewModel for row should have bool IsChecked get set property. For Top level "Select All" checkbox - you will need a top level bool? SelectAll get set property too - getter will be something like --
Lets says collection bound to GridView is
List<RowViewModel>RowViewModelCollection;
Then each RowViewModel object will have:
private bool _IsSelected = null;
public bool IsSelected
{
get => _IsSelected;
set
{
_IsSelected = value;
// NotifyProperty Change for this property
// Notify Property Change for TopLevel SelectAll property
}
}
Your Top level SelectAll should look like:
public bool? SelectAll
{
get
{
if(RowViewModelCollection.All(r=>r.IsSelected == true))
return true;
else if(RowViewModelCollection.All(r=>r.IsSelected == false))
return false;
else
return null;
}
set
{
foreach(var row in RowViewModelCollection)
{
row.IsSelected = value;
}
// Notify Property Changed for This property
}
}
SOLUTION IS IN EDIT OF THE ACCEPTED ANSWER
I have a view in which has two Pickers, I need to have it so that when the SelectedItem property in one Picker changes, the list of Items in the second Picker (ItemSource) changes as well.
Currently I have a bound the SelectedItem and SelectedIndex properties of both pickers to properties in my ViewModel. In the setter(s) for both of them, I perform the logic needed to change the list of Items in the second picker. The list of Items in the second picker changes successfully, but when I set the SelectedIndex (to make it select an Item by default), this fails if the index which I am setting it to is the same as the index which it was on in the previous list. It just shows the Title of the Picker instead, this issue seems to be related to this bug.
My Code:
Both Pickers are bound to an Observable collection of strings FYI
FrameType and DirectionType are Enums
I initially used only the SelectedItem property
Relevant XAML
<Label Grid.Row="1" Grid.Column="0" VerticalTextAlignment="Center"
Text="Direction: " />
<Picker x:Name="PickerDirection" Grid.Row="1" Grid.Column="1"
Title="Select Direction"
ItemsSource="{Binding Directions}"
SelectedItem="{Binding SelectedDirection}"
SelectedIndex="{Binding SelectedDirectionIndex}"></Picker>
<Label Grid.Row="2" Grid.Column="0" VerticalTextAlignment="Center"
Text="Frame: "/>
<Picker x:Name="PickerFrame" Grid.Row="2" Grid.Column="1"
Title="Select Frame"
ItemsSource="{Binding Frames}"
SelectedItem="{Binding SelectedFrame}"
SelectedIndex="{Binding SelectedFrameIndex}"></Picker>
Relevant View Model code:
public string SelectedDirection
{
get { return _selectedDirection; }
set
{
if (Directions.Contains(value))
{
if (_selectedDirection != value)
{
if (EnumUtils.ToEnumFromString<FrameType>(SelectedFrame) == FrameType.Road &&
!DirectionsRoad.Contains(value))
{
_selectedDirection = Directions[Directions.IndexOf(DirectionType.Right.ToString())];
}
else
{
_selectedDirection = value;
}
OnPropertyChanged();
}
}
}
}
public string SelectedFrame
{
get { return _selectedFrame; }
set
{
if (_selectedFrame != value)
{
_selectedFrame = value;
if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Road)
{
Directions = DirectionsRoad;
if (Directions.Contains(SelectedDirection))
{
SelectedDirectionIndex = Directions.IndexOf(SelectedDirection);
}
else
{
SelectedDirectionIndex = Directions.IndexOf(DirectionType.Right.ToString());
}
}else if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Lane)
{
Directions = DirectionsAll;
SelectedDirectionIndex = Directions.IndexOf(SelectedDirection);
}
OnPropertyChanged();
}
}
}
}
public int SelectedDirectionIndex
{
get { return _selectedDirectionIndex; }
set
{
if (_selectedDirectionIndex != value)
{
if (!(value < 0 || value >= Directions.Count))
{
_selectedDirectionIndex = value;
OnPropertyChanged();
}
}
}
}
public int SelectedFrameIndex
{
get { return _selectedFrameIndex; }
set
{
if (_selectedFrameIndex != value)
{
if (!(value < 0 || value >= Frames.Count))
{
_selectedFrameIndex = value;
OnPropertyChanged();
}
}
}
}
The outcome:
I expect it to never be empty since I ensure that the SelectedDirection is always set to something.
Notes:
I initially used just the SelectedItem property, but when I encountered this bug I thought using the SelectedIndex property would help to fix it.
I used ObservableCollection to maintain consistency with the other viewModels in the project, and to ensure that the options in the picker are updated when I make changes (based on my understanding you need to use ObservableCollection to make this possible).
I do plan to refactor the code in the setter for SelectedFrame into smaller functions as soon as I get things to work.
Due to this It seems that using the SelectedIndexChanged event of the Picker would be the only way to fix this. However the comment by ottermatic in this question says that events are unreliable. Hence I felt is was better to perform this logic in the setter.
If someone could comment on what I may be doing wrong in my code which is causing this issue and also comment on the pros/cons and/or whether or not I should use the eventHandler or have the logic in my setter. Thanks
I don't really see why you are using both SelectedItem and SelectedIndex, but I think what you are trying to achieve can be achieved easier.
First of all, I don't think you need ObservableCollection types at all in your example, since you are setting the directions anyway and not modifying the collection. More importantly, fiddling around with the indices is completely unnecessary, as far as I can tell. You are using strings anyway and even though String is not a value type, but a reference type, you cannot practically distinguish two String instances that have the same content, hence assinging the respective values to SelectedDirection and SelectedFrame is sufficient.
The following checks seem redundant to me
if (Directions.Contains(value))
if (EnumUtils.ToEnumFromString<FrameType>(SelectedFrame) == FrameType.Road &&
!DirectionsRoad.Contains(value))
since Directions are set to DirectionsRoad anyway if SelectedFrame has been set to "Road". Hence I'd assume that the second condition won't evaluate to true in any case. Hence the SelectedDirection can be simplified:
public string SelectedDirection
{
get => _selectedDirection;
set
{
if (_selectedDirection != value && Directions.Contains(value))
{
_selectedDirection = value;
OnPropertyChanged();
}
}
}
Within the setter of SelectedFrame there are many things going on, which I'd refactor to methods on it's own right, to improve clarity.
public string SelectedFrame
{
get => _selectedFrame;
set
{
if (_selectedFrame != value)
{
_selectedFrame = value;
UpdateAvailableDirections();
OnPropertyChanged();
}
}
}
private void UpdateAvailableDirections()
{
// store the selected direction
var previouslySelectedDirection = SelectedDirection;
Directions = GetValidDirectionsByFrameType(EnumUtils.ToEnumFromString<FrameType>(SelectedFrame));
SelectedDirection = GetSelectedDirection(previoslySelectedDirection, Directions);
}
private string[] GetValidDirectionsByFrameType(FrameType frameType)
{
return frameType == FrameType.Road ? DirectionsRoad : DirectionsAll;
}
private string GetSelectedDirection(string previouslySelectedDirection, string[] validDirections)
{
return validDirections.Contains(previouslySelectedDirection) ? previouslySelectedDirection : DefaultDirection;
}
By setting the SelectedItem instead of fiddling with the indices, the correct values shall be displayed.
Concerning your question whether this logic may be better suited in an event handler or in the setter depends on your requirements. If all you need is the index, the event SelectedIndexChanged may work out for you, but if the value is needed in several places and methods that are not called by the event handler, the presented solution may be more viable.
Edit
You were correct, it has got nothing to do with the usage of SelectedIndex and SelectedItem. The issue is a bit more subtle.
I build a quick proof-of-concept and found the following:
Assuming SelectedDirection is "Right" (and the index is set accordingly)
When Directions is set, the SelectedItem on the picker seems to be reset
SelectedDirection is set to null
this.Directions.Contains(value) evaluates to false, hence _selectedDirection is not set (this hold true for SelectedDirectionIndex, since the value -1 is filtered by if(!value < 0 || value >= this.Directions.Count))
When SelectedDirection is set afterwards, the value is still "Right", hence OnPropertyChanged is not called (since the values are the same) and the SelectedItem is not set
This way there is a mismatch between the value the Picker actually holds and the property in the viewmodel, which leads to unexpected behavior.
How to mitigate the issue?
I'd still stick with the code without the indices (unless you really need them) and use the string values.
There are other possibilities, but I'd change the setter of SelectedDirection. When you allowed the value to be set to null, PropertyChanged will be raised properly when the value is set to Right afterwards. If you really need to filter what the value is set to, you should still raise OnPropertyChanged, to inform the Picker that the value has changed (preventing a mismatch between the actual Pickers value and the viewmodel)
public string SelectedDirection
{
get => _selectedDirection;
set
{
if (_selectedDirection != value)
{
if(Directions.Contains(value))
{
_selectedDirection = value;
}
else
{
_selectedDirection = DirectionTypes.Right.ToString();
}
OnPropertyChanged();
}
}
}
Found a somewhat hacky fix for this, and it seems to be a Xamarin issue. I have made the following changes to my code"
The relevant changes are in the setter for SelectedFrame:
public string SelectedFrame
{
get { return _selectedFrame; }
set
{
if (_selectedFrame != value)
{
_selectedFrame = value;
if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Road)
{
Directions = DirectionsRoad;
if (Directions.Contains(SelectedDirection))
{
/*Relevant edits*/
var position = Directions.IndexOf(SelectedDirection);
SelectedDirection = Directions[Directions.Count - position -1];
SelectedDirection = Directions[position];
}
else
{
SelectedDirectionIndex = Directions.IndexOf(DirectionType.Right.ToString());
}
}else if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Lane)
{
Directions = DirectionsAll;
/*Relevant edits*/
var position = Directions.IndexOf(SelectedDirection);
SelectedDirection = Directions[Directions.Count - position -1];
SelectedDirection = Directions[position];
}
OnPropertyChanged();
}
}
}
}
It seems that my issue arises when I change the contents of my ObservableCollectoin but the SelectedDirection stays the same.
When I change Directions (which is an ObservableCollection) by assigning it to DirectionsAll (also an ObservableCollection), I need to make sure that the SelectedDirection changes,. The added code ensures that a change actually occurs to SelectionDirection and that fixes it. Seems somewhat hacky but it works.
Outcome:
I've created an example to illustrate my problem.
ViewModel:
public class VM : INotifyPropertyChanged
{
private double _value = 1;
public double Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
}
}
public VM()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromTicks(1);
timer.Tick += (s, e) => { Value += 1; };
timer.Start();
}
// OnPropertyChanged stuff ...
}
}
View:
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding Value, IsAsync=True, FallbackValue=Test}"/>
</Grid>
When running my application the text in the textbox flickers. During the update process the FallbackValue is displayed, which makes no sense to me.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed? Is there a way to display the old Value during an async update process?
This seems normal to me, given that you are using IsAsync=True in your binding. From the documentation:
While waiting for the value to arrive, the binding reports the FallbackValue, if one is available
When the PropertyChanged event is raised, WPF initiates the process of updating the target of the binding. Normally this would happen synchronously, with the property getter called immediately to update the value.
But you are using IsAysnc=True, so instead WPF fills in the target with the fallback value, and starts an asynchronous request to retrieve the actual property value later. Until that request has completed, the fallback value is displayed.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed?
Per the documentation, the intent behind the IsAsync=True setting is that it's used when the property getter is, or could be, slow. Your code has told WPF that the property value has changed, so it knows the old value is no longer valid. Your code has also told (via the IsAsync in the XAML) that the property getter could take some time to provide the new value, so it defers retrieving that value until later.
In the meantime, what should WPF display? That's what the fallback value is there for.
Is there a way to display the old Value during an async update process?
If you don't want the behavior that is designed for this feature in WPF, you should just retrieve the new data asynchronously yourself, and update the property via the setter when you have it. It's not a good idea for a property getter to be slow anyway, so this would be a better design in any case.
I had the same problem but with an image source. I've removed the IsAsync on the binding and I have made my getter async:
// This field hold a copy of the thumbnail.
BitmapImage thumbnail;
// Boolean to avoid loading the image multiple times.
bool loadThumbnailInProgress;
// I'm using object as the type of the property in order to be able to return
// UnsetValue.
public object Thumbnail
{
get {
if (thumbnail != null) return thumbnail;
if (!loadThumbnailInProgress) {
// Using BeginInvoke() allow to load the image asynchronously.
dispatcher.BeginInvoke(new Action(() => {
thumbnail = LoadThumbnail();
RaisePropertyChanged(nameof(Thumbnail));
}));
loadThumbnailInProgress = true;
}
// Returning UnsetValue tells WPF to use the fallback value.
return DependencyProperty.UnsetValue;
}
}
Sometimes a binding will fail, failure is important to consider. Fallback value option presents users a message if an error occurs, rather than nothing happening. If you would like your fallbackvalue to display the previous value contained, I could think of a few ways of trying : possibly saving the value in a reference string and/or to another control, then binding to that control
But if you don't want the fallbackvalue displayed at all, you need to do a code inspection to see how your binding is failing/or is slow, and contain it in your code behind
I've found an approach to avoid flickering by just inheriting from textbox and overriding it's textproperty-metadata.
Custom TextBoxControl
public class CustomTextBox : TextBox
{
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(CustomTextBox), new FrameworkPropertyMetadata(null, null, CoerceChanged));
}
private static object CoerceChanged(DependencyObject d, object basevalue)
{
var tb = d as TextBox;
if (basevalue == null)
{
return tb.Text;
}
return basevalue;
}
}
View
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<namespace:CustomTextBox Text="{Binding Value, IsAsync=True}"/>
</Grid>
It's important to have the text-binding without a fallbackvalue. So during update process the text is set to the textproperty defalut value - so in this case to null.
The CoerceChanged handler checks whether the new value is null. If it's so he returns the old value so that during update process there is still the old value displayed.
I am creating several .NET User Controls and I am trying to figure out the best way to go about setting properties. I have an address control and I am trying to create a property called ShowCountry which will either hide or show the control's country ddl.
I have been trying to set most of my properties similar to the below code:
public bool ShowCountry
{
get { return (bool)ViewState["ShowCountry"]; }
set
{
ViewState["ShowCountry"] = value;
pnlCountry.Visible = value;
}
}
How would I set a default value for this property? When I run my page with the control on it, it instantly errors out in the "get{}" when ShowCountry is used in one of my functions because I never set ShowCountry="false" in the control's tag. If I set this property when declaring the control everything works fine.
Also is what I am doing with the ViewState a good way to keep property values across postbacks?
Could someone show me how they would write this property?
The specs are:
Must keep value across postbacks, Must default to false
you can try this in order to avoiding error..
public bool ShowCountry
{
get {
if(ViewState["ShowCountry"] != null ){
return (bool)ViewState["ShowCountry"];
}
else {
//return the default value
return false;
}
}
set
{
ViewState["ShowCountry"] = value;
pnlCountry.Visible = value;
}
}
i think view state is best approach alternatively you can use hidden field in order to save value against post back.
I have a grid that is binded to a collection. For some reason that I do not know, now when I do some action in the grid, the grid doesn't update.
Situation : When I click a button in the grid, it increase a value that is in the same line. When I click, I can debug and see the value increment but the value doesn't change in the grid. BUT when I click the button, minimize and restore the windows, the value are updated... what do I have to do to have the value updated like it was before?
UPDATE
This is NOT SOLVED but I accepted the best answer around here.
It's not solved because it works as usuall when the data is from the database but not from the cache. Objects are serialized and threw the process the event are lost. This is why I build them back and it works for what I know because I can interact with them BUT it seem that it doesn't work for the update of the grid for an unkown reason.
In order for the binding to be bidirectional, from control to datasource and from datasource to control the datasource must implement property changing notification events, in one of the 2 possible ways:
Implement the INotifyPropertyChanged interface, and raise the event when the properties change :
public string Name
{
get
{
return this._Name;
}
set
{
if (value != this._Name)
{
this._Name= value;
NotifyPropertyChanged("Name");
}
}
}
Inplement a changed event for every property that must notify the controls when it changes. The event name must be in the form PropertyNameChanged :
public event EventHandler NameChanged;
public string Name
{
get
{
return this._Name;
}
set
{
if (value != this._Name)
{
this._Name= value;
if (NameChanged != null) NameChanged(this, EventArgs.Empty);
}
}
}
*as a note your property values are the correct ones after window maximize, because the control rereads the values from the datasource.
It sounds like you need to call DataBind in your update code.
I am using the BindingSource object between my Collection and my Grid. Usually I do not have to call anything.