I´m trying to use a MahApps Spin control in a login wpf form.
But the caliburn binding did not work.
<Controls:ProgressRing IsActive="{Binding Busy}" Grid.Row="6" Grid.Column="1" x:Name="Busy"/>
And my model has something like
bool _busy;
public bool Busy
{
get
{
return _busy;
}
set
{
_busy = value;
NotifyOfPropertyChange(() => _busy);
}
}
public void Login(string username, string password)
{
try
{
Busy = true;
...
But it not show the Spin control on the wpf form
thanks
Looking at your notification, you are notifying on the field rather than the property.
Change
NotifyOfPropertyChange(() => _busy);
to
NotifyOfPropertyChange(() => Busy);
Related
In reactive UI, BindCommand can bind some control to view model's property or method, e.g. method in ViewModel that will be executed when some button in XAML was clicked.
https://www.reactiveui.net/docs/handbook/commands/binding-commands
How do I disable or enable a set of buttons when some of them was clicked?
According to docs, BindCommand should have 3rd argument that can accept some function, but can't find an example.
XAML
<Button Content="Start" x:Name="StartButton" />
<Button Content="Stop" x:Name="StopButton" IsEnabled="False" />
<Button Content="Pause" x:Name="PauseButton" IsEnabled="False" />
XAML.cs
// How to enable Stop and Pause when Start was clicked?
this.BindCommand(ViewModel, vm => vm.Stop, view => view.StopButton).DisposeWith(container);
this.BindCommand(ViewModel, vm => vm.Start, view => view.StartButton).DisposeWith(container);
this.BindCommand(ViewModel, vm => vm.Pause, view => view.PauseButton).DisposeWith(container);
// In plain WPF I could do modify controls inside OnClick handler
private void OnStartClick(object sender, RoutedEventArgs e)
{
// How can I do this in Reactive UI?
StopButton.IsEnabled = true;
PauseButton.IsEnabled = true;
}
View Model
public DashboardViewModel(IScreen screen)
{
HostScreen = screen;
// Or how to get access to controls in these event handlers?
Stop = ReactiveCommand.Create(() => {});
Start = ReactiveCommand.Create(() => {});
Pause = ReactiveCommand.Create(() => {});
}
ReactiveCommand.Create accepts an IObservable<bool> that determines whether the value of CanExecute:
Start = ReactiveCommand.Create(() => { });
Stop = ReactiveCommand.Create(() => { }, Start.Select(_ => true));
Pause = ReactiveCommand.Create(() => { }, Start.Select(_ => true));
Considering that 3 persons, including me, voted for creating relevant properties in View Model and binding them in XAML, I did this first.
View Model
public ReactiveCommand<Unit, Unit> StopCommand { get; protected set; }
public ReactiveCommand<Unit, Unit> StartCommand { get; protected set; }
public bool StopState { get => _stopState; set => this.RaiseAndSetIfChanged(ref _stopState, value); }
public bool StartState { get => _startState; set => this.RaiseAndSetIfChanged(ref _startState, value); }
StopCommand = ReactiveCommand.Create(() =>
{
StopState = false;
StartState = true;
});
StartCommand = ReactiveCommand.Create(() =>
{
StopState = true;
StartState = false;
});
XAML
<Button Content="Start" IsEnabled="{Binding Path=StartState}" x:Name="StartButton" />
<Button Content="Stop" IsEnabled="{Binding Path=StopState}" x:Name="StopButton" />
That seemed like the most MVVM approach, even though not exactly a Reactive UI approach. Then, I found this answer that seems to be way more elegant and doesn't require hardcoded bindings between XAML and View Model.
What are the distinctions between the various WhenAny methods in Reactive UI
Using WhenAnyObservable I can subscribe to selected command and modify XAML from the code-behind, without creating a bunch of unnecessary properties in the View Model
this
.BindCommand(ViewModel, vm => vm.StartCommand, view => view.StartButton)
.WhenAnyObservable(o => o.ViewModel.StartCommand)
.Subscribe(o =>
{
StartButton.IsEnabled = false;
StopButton.IsEnabled = true;
})
.DisposeWith(container);
Done.
I'm using Fluent Ribbon in a MVVM design pattern, with a View/ViewModel. My goal is to change the body according to which tab is selected. I could use either SelectedTabItem or SelectedTabIndex and lookup the corresponding view. However, neither fire when the tab selection changes. In the View I have...
<Fluent:Ribbon Grid.Row="0" SelectedTabItem="{Binding SelectedRibbonTab}" SelectedTabIndex="{Binding SelectedRibbonTabIndex}">
</Fluent:Ribbon>
<ContentControl Grid.Row="1" Content="{Binding RibbonTabContent}"/>
In the ViewModel I have...
// Used both Item and Index for troubleshoothing, but need only one or the other for implementation
private IDictionary<string, FrameworkElement> RibbonTabViews;
private List<FrameworkElement> RibbonTabViewsList;
public RibbonTabItem SelectedRibbonTab
{
get
{
return selectedRibbonTab;
}
set
{
Update(() => SelectedRibbonTab, ref selectedRibbonTab, value, false);
UpdateContentControl();
}
}
public int SelectedRibbonTabIndex
{
get
{
return selectedRibbonTabIndex;
}
set
{
Update(() => SelectedRibbonTabIndex, ref selectedRibbonTabIndex, value, false);
UpdateContentControl(value);
}
}
public FrameworkElement RibbonTabContent
{
get { return ribbonTabContent; }
set { Update(() => RibbonTabContent, ref ribbonTabContent, value, false); }
}
protected void UpdateContentControl()
{
RibbonTabContent = RibbonTabViews[SelectedRibbonTab.Header.ToString()];
}
protected void UpdateContentControl(int index)
{
RibbonTabContent = RibbonTabViewsList[index];
}
I know I don't need both bindings but for the sake of troubleshoot I have both Item and Index. I would think in the ViewModel SelectedRibbonTab and SelectedRibbonTabIndex would be called each time the tab changed. Unfortunately, that doesn't appear to be the case. I have breakpoints at the setters and getters for each and neither are hit when changing tabs. What am I missing? I've been using this approach for years with Microsoft Ribbon but for some reason these don't fire in Fluent Ribbon. Any help would be great, thank you.
You have to set the binding mode to TwoWay to get updated values in your ViewModel.
i have a comboxbox that while it is beign populated i want it replaced in the UI by a message saying it is being loaded.
i did this by using a textbox showing the message and giving both objects visibility bindings in the view model (IsShowAuthComboBox &LoadingAuthenticationMsg)
here's the XAML code
<ComboBox x:Name="ComboBoxAuthSource"
Grid.Row="3"
Style="{StaticResource ComboBoxStyle}"
SelectedItem ="{Binding SelectedAuthenticationSource,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AuthenticationSource,UpdateSourceTrigger=PropertyChanged}"
Visibility= "{Binding IsShowAuthComboBox, Converter={StaticResource BoolToVis}}" />
<TextBox x:Name="ComboBoxAuthCover"
Grid.Row="3" Grid.Column="{StaticResource TableColumn}"
Style="{StaticResource FieldBoxStyle }"
FontSize="12"
IsReadOnly="True"
Visibility="{Binding IsShowGettingAuthenticationMsg, Converter={StaticResource BoolToVis}}"
Text="{Binding LoadingAuthenticationMsg,UpdateSourceTrigger=PropertyChanged,Mode=OneWay,FallbackValue='Loading authentication sources...'}" />
And here's the viewModel
public bool IsShowAuthComboBox
{
set
{
if (_isShowAuthenticationComboBox != value)
{
_isShowAuthenticationComboBox = value;
OnPropertyChanged("IsShowAuthComboBox");
OnPropertyChanged("IsShowGettingAuthenticationMsg");
}
}
get =>_isShowAuthenticationComboBox;
}
public bool IsShowGettingAuthenticationMsg => !_isShowAuthenticationComboBox;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
Log.Write(LogClass.General, LogLevel.Debug,
$"{propertyName} update triggerd",
_moduleName);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
this code is the first thing that happens in the relevant flow, but i will sometimes only see it at the very end of the execution and for only for an instant.
at other times it will work as expected.
what am i missing here?
EDIT :
this also accurs when validating the IP ,simpler code.
here's the code
public string SelectedServer
{
get => _selectedServer;
set
{
lock (_lockObj)
{
IsShowAuthComboBox = false;
if (!IsValideIp(value))
//some code
IsShowAuthComboBox = true;
}
}
bool IsValideIp(string ip)
{
//some code
//calls the server sync
return RemotingConfigurator.GetServerConfig(ip).isValid;
}
Your issue is that you are setting the IsShowAuthComboBox property and calling the IsValideIp synchronously on the same thread. And a single thread cannot both update the UI and query a database simultaneously.
What you should do is to call the IsValideIp on a background thread. I wouldn't do this in the setter of a property though, but rather in a command. You may want to read #Stephen Cleary's blog post on the subject.
this is what i ended up doing. moved the UI changes away from the data layer and into the viewModel (SetUiOnWait)
public string SelectedServer
{
get => _selectedServer;
set
{
//IsShowAuthComboBox = false;
SetUiOnWait(true);
Log.Write(LogClass.General, LogLevel.Debug,
$"Server changed from {_selectedServer} to {value} by user",
_moduleName);
_selectedServer = value;
OnPropertyChanged();
// OnPropertyChanged();
//workaround for when changing servers when a unique
//authentication source is selected causes the selected source to be null :\
if (AuthenticationSource.Any())
{
SelectedAuthenticationSource = AuthenticationSource[0];
}
Task.Factory.StartNew(() =>
{
LoginInfo.SelectedServer = _selectedServer;
}).ContinueWith((t) =>
{
if(t.Exception !=null)
{
ExceptionLog.Write(t.Exception.GetBaseException(),_moduleName);
}
RefreshAuthenticationProperties();
OnPropertyChanged("IsLimitedClinicalUse");
OnPropertyChanged("IsNotForClinicalUse");
SetUiOnWait(false);
});
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
dispatcher.Invoke((Action)(() =>
{
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}));
}
Task.Factory.StartNew() forces and logic to be executed on a new thread and for the UI changes to wait for it be completed.
and invoke within OnPropertyChange forces the event to be handled by the UI thread.
I have WPF MVVM application. There i have one user control with popup. When i click on one of the user control's button (Parent Bindings) i wish to show popup. (likewise close)
Command="{Binding Parent.ShowPopupCommand}"
<Popup Name="Popup1" IsEnabled="True"
IsOpen="{Binding DisplayHelper.IsOpenPopup, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
</Popup>
Instead of writing Dependency Property in user control i wrote separate view model with INotifyPropertyChanged interface. On login i am binding popup IsOpen property from Login.cs
RelayCommand _showPopupCommand;
RelayCommand _hidePopupCommand;
public ICommand ShowPopupCommand
{
get
{
if (_showPopupCommand == null)
{
_showPopupCommand = new RelayCommand(param => this.ShowPopup(), null);
}
return _showPopupCommand;
}
}
public ICommand HidePopupCommand
{
get
{
if (_hidePopupCommand == null)
{
_hidePopupCommand = new RelayCommand(param => this.HidePopup(), null);
}
return _hidePopupCommand;
}
}
private void HidePopup()
{
DisplayHelper ds = new DisplayHelper();
ds.IsOpenPopup = false;
}
private void ShowPopup()
{
DisplayHelper ds = new DisplayHelper();
ds.IsOpenPopup = true;
}
but popup is not showing on click.
Please help in this
Your problem is that you create new instances of DisplayHelper each time you run a command, but the View looks for a DisplayHelper property in the ViewModel.
To fix this, I suggest, you set the DisplayHelper as a property in the ViewModel.
I hope it helped, and if you need me to elaborate, feel free to ask. Happy Coding. :)
i have an XamDataPresenter (XamDataGrid) bound to a collection in the ViewModel:
XAML:
<igDP:XamDataPresenter x:Name="dataPresenter" DataSource="{Binding Path=AppServers, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True">
</igDP:XamDataPresenter>
Code:
public ShellViewModel()
{
AppServers = new BindingListCollectionView(new BindingList<AppServer>(_context.GetAllAppServers()));
AppServers.CurrentChanged += new EventHandler(AppServers_CurrentChanged);
}
void AppServers_CurrentChanged(object sender, EventArgs e)
{
NotifyOfPropertyChange(() => CanSaveAppServers);
NotifyOfPropertyChange(() => CanDeleteAppServers);
}
The CanSaveAppServers property:
public bool CanSaveAppServers
{
get
{
return (_appServers.SourceCollection as BindingList<AppServer>).Any(x => x.ChangeTracker.State != ObjectState.Unchanged);
}
}
The CanSaveAppServers property should be false if an item of the collection is changed. But how is the CanSaveAppServers called? Another event? Or the wrong collection type? Shouldn't this be done automatically in some way?
Thanks in advance.
If you are letting Caliburn bind via naming conventions, then you have a public method named SaveAppServers. Caliburn creates an ICommand that is bound to the Button so that when the button is clicked, ICommand's Execute() is called. In the meantime, there is a CanExecute() method on ICommand that is used to determine whether the button is enabled or not.
When you call NotifyOfPropertyChange(() => CanSaveAppServers), this ends up making the ICommand raise its CanExecuteChanged event, which makes WPF refresh by calling CanExecute() again, which under the covers is getting CanSaveAppServers.