I am updating a property in Viewmodel-A from Viewmodel-B while in View-B. In the same function that calls for the property update, the view is switched to View-A; The breakpoint I set on private async Task RepopulateFieldValues doesn't get hit.
So when I click on my button in View-B, I see all my relevant breakpoints get hit in Viewmodel-B, my view switches over to View-A, I see my breakpoint hit on the ICommand and thats it. Then, if I switch my view back to any other view, I see the breakpoint on my Tasks getting hit. I know I've got something backwards.
This is my property in Viewmodel-A thats updated via Viewmodel-B:
private int _ApptSelected;
public int ApptSelected {
get { return _ApptSelected; }
set {
_ApptSelected = (MainViewModel.ApptID.Count > 0) ? MainViewModel.ApptID[0] : 0;
}
}
Here is my ICommand:
public ICommand RepopulateFields_Command => new AsyncRelayCommand(RepopulateFieldValues);
This is the only way I could come up with to trigger the RepopulateFields_Command:
<TextBox x:Name="HackProperty"
Text="{Binding ApptSelected,
Mode=OneWay,
NotifyOnSourceUpdated=True,
UpdateSourceTrigger=PropertyChanged}" >
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="TextChanged">
<behaviors:InvokeCommandAction Command="{Binding RepopulateFields_Command}" />
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
</TextBox>
And this is the "cascading" Tasks Im trying to run:
private async Task RepopulateFieldValues()
{
if (ApptSelected > 0)
{
Console.WriteLine("ApptSelected: " + ApptSelected);
await RetrieveEditInfo(ApptSelected);
}
}
This is the relevant code within Viewmodel-B (for switching views)
public void EditSelection_Clicked(object obj)
{
if(Messaging.AskQuestion("Do you want to edit this appointment?"))
{
SelectedVM = AppointmentsViewModel.ApptTabItems[1];
AppointmentsViewModel.ApptTabItems[1].IsSelected = true;
ApptSelectedID = ApptIDSelected.ApptID;
Messaging.ShowAlert("ID: " + ApptSelectedID);
MainViewModel.ApptID.Clear();
MainViewModel.ApptID.Add(ApptSelectedID);
}
}
The EventTrigger that you are using only works with routed events and TextChanged is not a routed event. This means that your command won't be invoked when you type something into the TextBox.
But if you simply want to fire-and-forget the command, you should be able to execute it directly in the setter of the ApptSelected which is bound to the TextBox:
public int ApptSelected {
get { return _ApptSelected; }
set {
_ApptSelected = (MainViewModel.ApptID.Count > 0) ? MainViewModel.ApptID[0] : 0;
_ = RepopulateFieldValues();
}
}
Then you don't need to bother about the TextChanged event at all.
Related
googling for this showed me that this is often a problem but never reallay solved.
I do have an App/Prgramm in C#, i'll try to be mvvm conform.
I do have an window, in it a UserControl show different views.
One of my view contains a textbox, the text of the textbox is bound to a proptery of the VM.
My textbox got 2 inputbindings, for "enter" and "return" - both leading to same command.
On hitting "enter" the value of the textbox should be processed, the textbox shoud be cleared and refocused ... This works .... One Time ....
Clearing the textbox with String.Empty breaks the Bindings ... this could be found in several postings here ... the most Solution is textboxname.clear() ...
But i dont have the "textboxname" in the viewmodel, only in code-behind, but all my logic is in the VM ... So can somebody pls help me sort things out, how i could clear the textbox without breaking the bindings to input text and hit enter more then one time ?
My Code
<TextBox x:Name="tb_checkinbox" Grid.Row="0" Grid.Column="1" Width="200" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding CheckInNumber}">
<TextBox.InputBindings>
<KeyBinding Command="{Binding OnCheckInEnterCommand}" Key="Return"/>
<KeyBinding Command="{Binding OnCheckInEnterCommand}" Key="Enter"/>
</TextBox.InputBindings>
</TextBox>
public CheckinVM()
{
OnCheckInEnterCommand = new RelayCommand(OnCheckInEnter, CanCheckInEnter);
}
private string _checkInNumber;
public string CheckInNumber
{
get { return _checkInNumber; }
set { SetProperty(ref _checkInNumber, value); }
}
public ICommand OnCheckInEnterCommand { get; set; }
public void OnCheckInEnter(object value)
{
CheckInNumber = String.Empty;
/// More to do
}
public bool CanCheckInEnter(object value)
{
return true;
}
The assignment
CheckInNumber = string.Empty;
does not "clear" any Binding. Your conclusion is wrong.
You do however only get empty strings - after clearing - in the setter of the CheckInNumber property. In order to get property updates not only when the TextBox loses focus or you reset the source property, set UpdateSourceTrigger=PropertyChanged on the Text Binding:
<TextBox ... Text="{Binding CheckInNumber, UpdateSourceTrigger=PropertyChanged}">
I want to change something in the UI side after a button click. Let's say changing button width after clicking the button.
<Button x:Name="btnButt" Content="Button"/>
Then in my ViewModel,
View.MyDialog mydialog = new View.MyDialog();
mydialog.btnButt.Width = 3000;
However, everything is not working. I also tried binding a string from the ViewModel:
\\ XAML
<Button x:Name="btnButt" Content="Button" Width="{Binding WidthVM}"/>
\\ ViewModel
public int WidthVM = 3000;
What could be missing?
I will not recommend your first solution because it would kill the purpose of MVVM.
Your second solution is actually correct which is to bind some data from the VM. It did not work because you need to have it in MVVM way to send signal changes to the UI, have it like this:
public const string WidthVMPropertyName = "WidthVM";
private int _widthVM = 0;
public int WidthVM
{
get
{
return _widthVM;
}
set
{
Set(WidthVMPropertyName, ref _widthVM, value);
}
}
\\ then set it like:
WidthVM = 3000;
I am struggling with an update to a ComboBox that previously worked. I originally had its ItemsSource bound to a read-only ObservableCollection<char> property in the ViewModel. When the user instigates changes (which is done with mouse strokes, so dozens of times per second in some cases), the get rebuilds the collection from the Model and returns it.
When I changed to my own object in the ObservableCollection, the ComboBox started flickering during updates. I'm not sure what's going wrong. Here's the code that works, starting with the XAML:
<ComboBox ItemsSource='{Binding FromBins}' SelectedValue='{Binding SelectedFromBin, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}' />
ViewModel:
public ObservableCollection<char> FromBins
{
get
{
ObservableCollection<char> tempBins = new ObservableCollection<char>();
foreach (var item in Map.BinCounts)
{
tempBins.Add(item.Key);
}
return tempBins;
}
}
I simply raise a property change with every mouse movement and the interface works as expected (there is some other logic to ensure the SelectedItem is valid).
To make the interface more useful, I decided to add more information to the ComboBox, using my own class:
public class BinItem : IEquatable<BinItem>
{
public char Bin { get; set; }
public SolidColorBrush BinColor { get; set; }
public string BinColorToolTip { get {...} }
public BinItem( char bin )
{
Bin = bin;
BinColor = new SolidColorBrush(BinColors.GetBinColor(bin));
}
public bool Equals(BinItem other)
{
return other.Bin == Bin ? true : false;
}
}
If I swap char out for BinItem in the working code ViewModel I get flickering as the mouse is moved. Here is the updated XAML:
<ComboBox ItemsSource='{Binding FromBins}' SelectedValue='{Binding SelectedFromBin, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}'>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" ToolTip='{Binding BinColorToolTip}'>
<Rectangle Fill='{Binding BinColor}' Width='10' Height='10' HorizontalAlignment='Center' VerticalAlignment='Center' Margin='0,0,4,0' Stroke='#FF747474' />
<TextBlock Text="{Binding Bin}" Width='16' />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I have tried numerous things, including but not limited to:
-Using a List instead of the ObservableCollection but even though the Get fires every time and returns the correct collection of items, the interface does not always update (though the flickering disappears).
-Leaving all possible bins in the items source and adding a Visibility property to the BinItem class that I bound to (couldn't get it to update).
I suspect I am doing something fundamentally wrong, but no amount of searching SO or otherwise has helped thus far. Any help is appreciated.
I was able to solve this using the ideas from Clemens and Chris. Not sure if this is the most elegant solution, but it works as intended with no measurable performance hit.
Instead of replacing the collection with each refresh, I go through the logic of finding out what's changed (with each update there could be an addition AND a removal simultaneously). Code below:
private ObservableCollection<BinItem> _FromBins = new ObservableCollection<BinItem>();
public ObservableCollection<BinItem> FromBins
{
get
{
if (_FromBins.Count > 0)
{
List<char> BinsToRemove = new List<char>();
foreach (var item in _FromBins)
{
if (!Map.BinCounts.ContainsKey(item.Bin))
{
BinsToRemove.Add(item.Bin);
}
}
foreach (var item in BinsToRemove)
{
_FromBins.Remove(new BinItem(item));
}
}
foreach (var item in Map.BinCounts)
{
if (!_FromBins.Contains(new BinItem(item.Key)) && item.Value > 0) {
_FromBins.Add(new BinItem(item.Key));
}
}
return _FromBins;
}
}
Hope this can help someone else too.
I have a slider which binds to a property with slow getter and setter. Since the UI needs to be responsive, the binding has the attribute IsAsync. But when I drag the slider it jumps between the cursor and 0 (default FallbackValue).
Does anybody know how to prevent this behavior, how to disable the FallbackValue?
XAML:
<Slider>
<Slider.Value>
<Binding Path="Value" IsAsync="True"/>
<!-- For testing, best plan till now
<PriorityBinding>
<Binding Path="Value" IsAsync="True"/>
<Binding Path="CurrentValue"/>
</PriorityBinding>
-->
</Slider.Value>
</Slider>
Code Behind:
public int Value
{
get
{
//if (setterCount > 0) // For testing, best plan till now
//{
// return CurrentValue;
//}
Thread.Sleep(100); // Just for simulating slow functionality
return myValue;
}
set
{
//++setterCount; // For testing, best plan till now
//CurrentValue = value;
Thread.Sleep(200); // Just for simulating slow functionality
myValue = value;
//--setterCount;
OnPropertyChanged("Value");
}
}
Just don't call blocking operations inside a property, they aren't meant for this.
Bind the value normally and query/validate it's input inside a command.
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<cmd:EventToCommand Command="{Binding UpdateSomethingCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
And do the async in your command
public ICommand UpdateSomethingCommand { get; } = new RelayCommand(UpdateSomething);
private async void UpdateSomething()
{
await SomeLengthlyCallOrValidation(this.SliderValue);
}
I don't know if this solution can be considered "pure MVVM" but let's propose it and see if it fits you.
First of all, we define a Slider and subscribe to the Thumb.DragCompleted event. We also set UpdateSourceTrigger to explicit:
<Slider Maximum="100" Minimum="0" Width="300" x:Name="slid" Thumb.DragCompleted="MySlider_DragCompleted" >
<Slider.Value>
<Binding Path="Value" UpdateSourceTrigger="Explicit"/>
</Slider.Value>
</Slider>
Now in the event handler in codebehind we do this:
private void MySlider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
BindingExpression be = slid.GetBindingExpression(Slider.ValueProperty);
be.UpdateSource();
}
Summing up, what we are doing here is just doing the binding (getting/setting the slider value) only when the "sliding" process is completed.
There is no need to make getter asynchronous, simply store (cache) last value.
You can bind to a property normally and implement asynchronous update yourself:
view:
<Slider Value="{Binding Value, Delay=100}" Maximum="..." />
viewmodel:
Task _task = Task.Run(() => { }); // Task.CompletedTask in 4.6
volatile int _id;
double _value;
public double Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
// task
var id = ++_id; // capture id
_task = _task.ContinueWith(o =>
{
if (_id == id)
{
Thread.Sleep(1000); // simulate work
// this is optional if value stays the same
if (_id == id)
{
_value = value + 10; // simulate different value
Dispatcher.InvokeAsync(() => OnPropertyChanged(nameof(Value)));
}
}
});
}
}
The idea: record slider value changes and execute only last one.
Recording is done by chaining tasks with ContinueWith. At first you have completed task, every change of Value will create a new task.
I don't know if there is a way to check whenever current task has continuation, therefore _id is used for this. When _id == id task is the last in the chain (otherwise task do nothing, as there are more actual updates).
Second if is used to check whenever after work there are more tasks and prevent value to change back (effect you have currently). This notification is needed if your work can return different value than the one what was set. Otherwise remove second if completely (it's not needed).
Notice Delay in binding, it will reduce number of tasks (without it one movement of slider may produce dozens of task).
I am trying to dynamically show/hide button inside Xamarin Forms ContentPage.
I have two buttons in my XAML code:
<StackLayout Orientation="Vertical">
<Button x:Name="start_btn" Clicked="startPanic">
<Button.Text>START</Button.Text>
</Button>
<Button x:Name="stop_btn" IsVisible="false">
<Button.Text>STOP</Button.Text>
</Button>
</StackLayout>
Corresponding C# code:
public partial class PanicPage : ContentPage
{
private Button startBtn;
private Button stopBtn;
public PanicPage ()
{
InitializeComponent ();
startBtn = this.FindByName<Button> ("start_btn");
stopBtn = this.FindByName<Button> ("stop_btn");
}
private void startPanic(object sender, EventArgs args){
Device.BeginInvokeOnMainThread (() => {
startBtn.IsVisible = false;
stopBtn.IsVisible = true; // DOESN'T WORK, button still will be hidden
});
}
}
When I set isVisible property in XAML, it doesn't react for any property change in event method (startPanic). How can I fix it?
Change your code in xmal file and write properties for start and stop button
<Button x:Name="start_btn" Clicked="startPanic" IsVisible="{Binding IsStartVisible}">
<Button.Text>START</Button.Text>
</Button>
<Button x:Name="stop_btn" IsVisible="{Binding IsStopVisible}">
<Button.Text>STOP</Button.Text>
</Button>
In ViewModel write following property and similar for start button and set IsStopVisible =true/false based on your logic
private bool _isStopVisible;
public bool IsStopVisible{
get {
return _isStopVisible;
}
set {
_isStopVisible= value;
RaisePropertyChanged ("IsStopVisible");
}
}
Maybe I'm late but I was searching this too without success. This may be useful for someone.
objectView.SetValue(IsVisibleProperty, false); // the view is GONE, not invisible
objectView.SetValue(IsVisibleProperty, true);
It should work just fine. I copied your code and cleaned it up a bit, it shows the STOP button, then I
A few remarks:
use the short property where possible <Button Text="X"/>, it's
easier to read
when you add a XAML page the IDE adds a .xaml.cs file next to it and generates another .g.cs that you don't see. The .g.cs file
contains generated code that finds all the x:Name'd elements and
defines placeholders for them, no need to find them by name yourself
all UI-initiated events are executed on the UI thread, no need to do that explicitly
Here's the XAML, same as yours just tighter and added Margin so the button is visible
<StackLayout Orientation="Vertical" Margin="20">
<Button x:Name="start_btn" Clicked="startPanic" Text="START" />
<Button x:Name="stop_btn" Text="STOP" IsVisible="false" />
</StackLayout>
And the code behind:
public partial class TestPage : ContentPage
{
public TestPage ()
{
InitializeComponent ();
}
private void startPanic(object sender, EventArgs args){
Device.BeginInvokeOnMainThread (() => {
start_btn.IsVisible = false;
stop_btn.IsVisible = true;
});
}
}
Use the Visibility property of view.
for example if u want to make your button invisible you can do
if(condition)
{
button.Visibility=ViewStates.Invisible;
}
else
{
button.Visibility=ViewStates.Visible;
}