I have a login button that I would like to apply an indeterminate progress look to while the login process is happening.
Here is the XAML for the button:
<Button x:Name="LoginButton" Style="{StaticResource MaterialDesignRaisedButton}"
materialDesign:ButtonProgressAssist.Value="-1"
materialDesign:ButtonProgressAssist.IsIndicatorVisible="false"
materialDesign:ButtonProgressAssist.IsIndeterminate="true">
LOGIN
</Button>
So I figure I can just bind a boolean property on my view model to materialDesign:ButtonProgressAssist.IsIndicatorVisible. I'm using code behind binding like so:
public partial class Connection : ReactiveUserControl<ConnectionViewModel>
{
public Connection()
{
InitializeComponent();
ViewModel = ViewModelLocator.ConnectionViewModel;
this.WhenActivated(d =>
{
this.BindCommand(ViewModel, vm => vm.LoginCommand, v => v.LoginButton).DisposeWith(d);
// How do I bind to this property using OneWayBind?
this.OneWayBind(ViewModel, vm => vm.LoggingIn, v => v.LoginButton.ButtonProgressAssist.IsIndicatorVisible).DisposeWith(d);
});
}
}
Intellisense doesn't pick up on that material designs dependency property. How do I reference it?
In case it matters, the WPF project targets .NET Core 3.1
I don't think the OneWayBind method supports attached properties but you could bind to it in the XAML markup:
materialDesign:ButtonProgressAssist.IsIndicatorVisible="{Binding LoggingIn, Mode=OneWay}"
You can of course do OneWayBind for the other properties just like before.
Related
I got a simple WinForm application with a couple of textboxes and a confirm button, I'm using ReactiveUI.
This is my ViewModel:
public CurrencyViewModel()
{
editCurrency = new Currency();
this.ValidationRule(
viewModel => viewModel.IsoCode,
isoCode => !string.IsNullOrWhiteSpace(isoCode),
"error");
this.ValidationRule(
viewModel => viewModel.Name,
name => !string.IsNullOrWhiteSpace(name),
"error");
NewCommand = ReactiveCommand.Create(() => NewItem());
SaveCommand = ReactiveCommand.Create(() => Save(), this.IsValid());
}
public string IsoCode
{
get => isoCode;
set
{
editCurrency.IsoCode = value;
this.RaiseAndSetIfChanged(ref isoCode, value);
}
}
public string Name
{
get => name;
set
{
editCurrency.Name = value;
this.RaiseAndSetIfChanged(ref name, value);
}
}
private void NewItem()
{
IsoCode = string.Empty;
Name = string.Empty;
Symbol = string.Empty;
}
I then bind my validation and my save command in the view:
this.BindValidation(ViewModel, vm => vm.IsoCode, v => v.errorLabelIsoCode.Text).DisposeWith(disposables);
this.BindValidation(ViewModel, vm => vm.Name, v => v.errorLabelName.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCommand, v => v.sfButtonOk, nameof(sfButtonOk.Click)).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.NewCommand, v => v.sfButtonNew, nameof(sfButtonNew.Click)).DisposeWith(disposables);
My issue is that sfButtonOk stays enabled when i first launch the application even if isValid() is false, the command doesn't fire as intended so it's just a grapichal problem it seems. The button is disabled only if I write valid text and then cancel it.
It seems that the button is disabled only when isValid goes from true to false
The issue here is probably related to a view model being initialized too late, or due to the view model property not sending change notifications on the view side in time. Make sure you assign the view model to the IViewFor.ViewModel property before a call to WhenActivated, or otherwise implement INotifyPropertyChanged on the view side (also you probably don't need a WhenActivated at all because WinForms doesn't have dependency properties that might introduce a potential for a memory leak)
Additionally worth noting, that we have evergreen sample apps targeting various UI frameworks including Windows Forms in the ReactiveUI.Validation core repository https://github.com/reactiveui/ReactiveUI.Validation/blob/d5089c933e046c5ee4a13149491593045cda161a/samples/LoginApp/ViewModels/SignUpViewModel.cs#L43 Just tested the Winforms sample app, and the button availability seems to perform as we'd expect.
I've been experimenting with async commands using F# Viewmodule. The problem is when I click the button, the command get executed, but afterwards the button stays disabled.
Xaml:
<Button Content="start async worker" Command="{Binding StartAsyncCommand}" />
ViewModel:
type MainViewModel() as me =
inherit ViewModelBase()
//...
member __.StartAsyncCommand = me.Factory.CommandAsync(fun _ -> async { return () } )
What am I doing wrong?
EDIT:
Thanks to #FoggyFinder, we determined that the issue was actually with the App.fs file:
open System
open FsXaml
open System.Windows
type MainWindow = XAML< "MainWindow.xaml">
[<STAThread>]
[<EntryPoint>]
let main argv =
Application().Run(MainWindow().Root)
Creating basically empty App.xaml and starting like this:
module main
open System
open FsXaml
type App = XAML<"App.xaml">
[<STAThread>]
[<EntryPoint>]
let main argv =
App().Root.Run()
fixed it. If anyone knows the explanation for this, don't hesitate to provide it.
What am I doing wrong?
The problem is that there is no SynchronizationContext in place when you construct your ViewModel layer, which means that the internal code that pushes things back to the UI context don't work properly.
You can work around this by adding the following at the beginning of your entry point, before you call Application.Run():
if SynchronizationContext.Current = null then
DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher)
|> SynchronizationContext.SetSynchronizationContext
This will make sure the Dispatcher is created and a valid SynchronizationContext is installed, and will likely fix the issue for you.
I used asynchronous commands, but this problem never arose. I tried to reproduce your code - everything is working fine. Are you sure you gave complete code?
Try to run the code:
.xaml:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Button Content="start async worker" Command="{Binding StartAsyncCommand}" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" />
<TextBlock Text="{Binding Count}" Margin="5" HorizontalAlignment="Right" VerticalAlignment="Top"></TextBlock>
</Grid>
ViewModel:
type MainViewModel() as me =
inherit ViewModelBase()
let count = me.Factory.Backing(<# me.Count #>, 0)
member __.StartAsyncCommand = me.Factory.CommandAsync(fun _ -> async { count.Value <- count.Value + 1 })
member __.Count with get() = count.Value
About the differences between
let dosomething _ = async { return () }
member __.StartAsyncCommand = me.Factory.CommandAsync(dosomething)
and :
member __.StartAsyncCommand = me.Factory.CommandAsync(fun _ -> async { return () } )
look this answer:
https://chat.stackoverflow.com/transcript/message/26511092#26511092
The easy workaround is to use interaction triggers:
<Button>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Click">
<fsx:EventToCommand Command="{Binding StartAsyncCommand}" />
</ia:EventTrigger>
</ia:Interaction.Triggers>
</Button>
Just bind ItemSource to Silverlight ComboBox. Do key navigation within a combobox where DropDown should not be opened. after completing key navigation click drop down icon to view the drop down list. There are multiple items selected that has same value, some times different value selected.
Is there any way to overcome this issue ? Or is that framework issue?
Details:
My combobox xaml is here:
<ComboBox ItemsSource="{Binding Path=ComboBoxItemsSource}" Grid.Column="1" Width="150" Height="40"/>
where ComboBoxItemsSource is List of String collection that defined in ViewModel.
ViewModel
string[] productName = new string[]
{
"Alice Mutton",
"NuNuCa Nuß-Nougat-Creme",
"Boston Crab Meat",
"Raclette Courdavault",
"Wimmers gute Semmelknödel",
"Gorgonzola Telino",
"Chartreuse verte",
"Fløtemysost",
"Carnarvon Tigers",
"Thüringer Rostbratwurst",
"Vegie-spread",
"Tarte au sucre",
"Konbu",
"Valkoinen suklaa",
"Queso Manchego La Pastora",
"Perth Pasties",
"Vegie-spread",
"Tofu",
"Sir Rodney's Scone 7",
"Manjimup Dried Apples"
};
private List<string> _comboBoxItemsSource = new List<string>();
public List<string> ComboBoxItemsSource
{
get { return _comboBoxItemsSource; }
set { _comboBoxItemsSource = value; }
}
public ViewModel()
{
_comboBoxItemsSource = productName.ToList();
}
The Setting should be Button, ComboBox.
First get focus on Button.
Then press tab to focus combo box.
Now, Just do pressing - right/left/up/down keys continuously.
Now click on drop down icon. you can see that multiple items
selected.
my problem is:
i made a combobox and i want to use context menu on it's elements, so when i'm setting the cellfactory as shown below, i can't see the items in any more and the context menu does not show.
CBXGroups.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
public ListCell<String> call(ListView<String> param) {
final ListCell<String> cell = new ListCell<String>();
final ContextMenu cellMenu = new ContextMenu();
MenuItem rimuoviDalControllo = new MenuItem("RIMUOVI DAL CONTROLLO");
MenuItem rimuoviDefinitivamente = new MenuItem("RIMUOVI DEFINITIVAMENTE");
rimuoviDalControllo.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Service.deleteGroupFromControl(cell.getText(),CBXControllo.getSelectionModel().getSelectedItem());
populateLists();
}
});
rimuoviDefinitivamente.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Service.deleteGroup(cell.getText());
populateLists();
}
});
cellMenu.setOnShowing(new EventHandler<WindowEvent>() {
public void handle(WindowEvent event) {
cell.requestFocus();
}
});
cellMenu.getItems().addAll(rimuoviDalControllo,rimuoviDefinitivamente);
cell.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(cell.itemProperty())).then(cellMenu).otherwise((ContextMenu) null));
return cell;
}
});
You can't see the items because you haven't set the text in your ListCell. You can do this with a one-liner:
cell.textProperty().bind(cell.itemProperty());
The context menu is trickier, and I don't really have a solution for it. The issue is that the ComboBox uses a PopupControl to display the list view, and the popup control has autoHide set to true. So when you click on the list view, the popup closes (preventing you seeing the context menu). There's no way to access the popup control, so I don't think there's going to be any way to do this.
Registering a context menu with items in a combo box seems like an unusual thing to do; I wonder if there is a better approach for what you want to do. A MenuButton is similar to a ComboBox in some ways (control that displays a popup with options), but it has a menu hierarchy so you can include cascading menus. This might provide the kind of functionality you want.
I've already done it quite easily in the past with Silverlight, by declaring a BusyIndicator as my root element, and binding the IsBusy property to the IsLoading property of the domain context generated by RIA Services:
<toolkit:BusyIndicator IsBusy="{Binding Context.IsLoading}" >
Since there seems to be no IsLoading property on the ObjectContext generated by Entity Framework, how can I bind the IsBusy property in WPF?
Thank you
What I came up with:
Busy Indicator from the WPF Extended Toolkit:
<extoolkit:BusyIndicator IsBusy="{Binding IsBusy}" BusyContent="Loading data..." >
In my base class view model, I've added the following method:
protected void ExecuteBackgroundProcess(Action action)
{
IsBusy = true;
Task task = Task.Factory.StartNew(() => action()).ContinueWith((s) => this.IsBusy = false);
}
When I want to load a collection from the server, I can call from a derived view model:
this.ExecuteBackgroundProcess(() =>
{
var collection = _securityRepo.TakeOfType<Security>(10).ToObservableCollection();
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
Securities = collection;
RaisePropertyChanged("Securities");
});
});
There's also a more robust & complete solution on CodeProject:
http://www.codeproject.com/KB/WPF/ThreadingComponent.aspx?msg=3319891