Caliburn Micro void function can't be triggered inside DialogHost - c#

Normally you can call void function(from ViewModel) in View by simply:
Button Name = VoidFunctionInViewModel
Button Command={Binding Path=VoidFunctionInViewModel}
But when accessing this function inside a DialogHost, the void function isn't triggered.
I have tried retrieving a string field to the DialogHost and it works fine, but when it comes to commands on buttons it doesn't work.
MainViewModel.cs Commands:
public async void OpenDialog()
{
var confirm = new ConfirmationView{DataContext = this};
await DialogHost.Show(confirm);
}
public void Accept()
{
Console.WriteLine("It Failed!");
}
public string Success {get; set;} = "Success"
ConfirmationView.xaml:
<Button Command="{Binding Path=Accept}" Content="Accept"/>
<Button Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" Content="Cancel"/>
<TextBlock Name="Success"/>
MainView.xaml:
<materialDesign:DialogHost DialogTheme="Inherit" CloseOnClickAway="True">
</materialDesign:DialogHost>
The Property "Success" is successfully used and shown by the DialogHost.
But the Button "Accept" with the command Accept isn't triggered by the DialogHost.
The Button "Cancel" is working, with the command from materialDesign.

Shouldnt it be this way for caliburn.micro?
<Button x:Name="Accept" Content="Accept"/>
Using the Command="" syntax works only by using ICommand or RelayCommand in the ViewModel, or? At least that is how i understood caliburn.micro so far.
If that doesnt work, you could try this. This worked for me in the drawerHost, where caliburn.micro Command binding failed for me:
<Button cal:Message.Attach="[Event Click] = [Action Accept()]" Content="Accept" />
other sources with dialog examples, which might be usefull:
https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/wiki/Dialogs
https://github.com/Keboo/MaterialDesignInXaml.Examples

Related

How to allow pressing only the cancel button when the value is invalid?

I have a simple dialog with a SpinEdit and two buttons: OK_Button and Cancel_Button. I've set a mask for the value in the SpinEdit and the dialog won't let me press the cancel button when the value is invalid. I've tried changing the SpinEdit's property to InvalidValueBehavior="AllowLeaveEditor" but then I can click both, OK and cancel button. Is there a way to ONLY allow pressing cancel when the value is incorrect?
XAML:
<dxe:SpinEdit x:Name="dxSpinEdit"
Height="23" MinWidth="200" Width="Auto"
HorizontalAlignment="Right"
Text="{Binding Value, Mode=TwoWay}"
MaskType="Numeric"
IsFloatValue="{Binding FloatValue}"
MinValue="{Binding MinValue}"
MaxValue="{Binding MaxValue}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"
MaskShowPlaceHolders="{Binding ShowPlaceHolder}"
InvalidValueBehavior="WaitForValidValue"
/>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" PreviewMouseDown="win_PreviewMouseDown" />
</StackPanel>
I looked up this issue on the devexpress forum but their solution didn't work for me. I've implemented the MouseDownPreview event like so:
C# (code behind)
private void OK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void win_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if(e.Source == Cancel_Button)
{
DialogResult = false;
Close();
}
}
But the event wasn't handled at all. I'd like to keep the property InvalidValueBehavior at the value "WaitForValidValue" but at the same time I'd like to allow pressing the Cancel button.
Even if you're not going to go the full MVVM route, you should switch from using click events to an ICommand implementation that supports CanExecute logic (such as this one from MVVM Light).
Using a command will automatically disable any bound control (e.g. button or menu item) when CanExecute is false. You can then have all the logic for controlling your commands grouped in one place, including validation that will only allow OK to be clicked when your object is in a valid state.
If you just want to go the standard WPF (non MVVM) route, you could add something like this in your window's constructor
public MyView()
{
....
Ok_Button.Command =
new RelayCommand(() => DialogResult = true, // just setting DialogResult is sufficient, no need to call Close()
// put the required validation logic here
() => dxSpinEdit.Value > 0 && dxSpinEdit.Value < 10);
Cancel_Button.Command = new RelayCommand(() => DialogResult = false);
// replace this with the actual event from SpinEdit
dxSpinEdit.ValueChanged += (s,e) => (OK_Button.Command as RelayCommand).RaiseCanExecuteChanged();
}
Yes I know it looks ugly 😀 - I'd suggest following the MVVM design pattern instead. When using MVVM, all of the command functionality belongs in your ViewModel.
Either way, you can then remove all the click and mousedown handlers from your buttons.

Command binded to the button is fired twice

Users of my WPF Application from time to time are able to fire twice command that is binded to button.
XAML Code:
<Button x:Name="btnAccept"
Style="{StaticResource FlatButtonLarge}"
Height="42"
Command="{Binding Path=SubmitCmd}"
Content="Submit" />
I've got also KeyBindings
<Window.InputBindings>
<KeyBinding Key="F9" Command="{Binding SubmitCmd}" />
</Window.InputBindings>
I am not able to reproduce an error but based on the changes in the database I conclude the command was fired twice one time after time. Is it really posible and how can I prevent such phenomena.
SubmitCmd adds new record to the database and closes form.
Here is the code behind:
vm.SubmitCmd = new RelayCommand(pars => DoSubmit(), pars => vm.CmdSubmitCanExecute, "Submit" );
private void DoSubmit()
{
try
{
if (!vm.LaunchAllowed)
{
this.Close();
}
else
{
vm.LaunchAllowed = false;
bool isOk = DBService.SaveToDB(vm.Dto);
if (isOk)
{
DialogResult = true;
this.Close();
}
else
{
ShowError(this, result);
vm.LaunchAllowed = true;
}
}
}
catch (Exception ex)
{
ShowError(this, ex.Message);
vm.LaunchAllowed = true;
}
}
And ViewModel code:
public ICommand SubmitCmd{ get; set; }
public bool CmdSubmitCanExecute
{
get
{
return LaunchAllowed;
}
}
I have same issue.
In my case i bind a command into two component, here is the wrong code
<pv:PancakeView Grid.Row="3" CornerRadius="30" Margin="24,8,24,16" BackgroundColor="{StaticResource MainGreen}" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" HasShadow="True"
xct:TouchEffect.PressedOpacity="0.7"
xct:TouchEffect.NormalOpacity="1"
xct:TouchEffect.AnimationEasing="{x:Static Easing.CubicInOut}"
xct:TouchEffect.AnimationDuration="100"
xct:TouchEffect.PressedScale="0.9"
xct:TouchEffect.Command="{Binding Checkout}">
<Label Text="CHECKOUT" TextColor="{StaticResource MainWhite}" FontSize="18" FontFamily="{StaticResource PoppinsMedium}" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
<pv:PancakeView.Shadow>
<pv:DropShadow Offset="0,0" Opacity="0.15" BlurRadius="25"></pv:DropShadow>
</pv:PancakeView.Shadow>
<pv:PancakeView.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Checkout}"/>
</pv:PancakeView.GestureRecognizers>
</pv:PancakeView>
At that xaml code, i bind Checkout Command to TapGestureRecognizer and xct:TouchEffect
Remove one of them solve the problem for me.
for your case, i think below approach will work
In KeyBindings area change the binded command to new command like this <KeyBinding Key="F9" Command="{Binding OtherSubmitCmd}" />
Add public ICommand OtherSubmitCmd{ get; set; } to your ViewModel. Then assign same methode for OtherSubmitCmd.
I think I saw this too, but - as you - I was not able to reproduce it. It also was my conclusion.
To solve this I disabled the button right after the command run. So assuming you using mvvm, add a property to it (don't forget to raise the property changed event) and bind the IsEnabled-Property of your button the the new property

MVVM, DialogService and Dialog Result

I'm currently learning WPF/MVVM, and have been using the code in the following question to display dialogs using a Dialog Service (including the boolean change from Julian Dominguez):
Good or bad practice for Dialogs in wpf with MVVM?
Displaying a dialog works well, but the dialog result is always false despite the fact that the dialog is actually being shown. My DialogViewModel is currently empty, and I think that maybe I need to "hook up" my DialogViewModel to the RequestCloseDialog event. Is this the case?
does your DialogViewmodel implement IDialogResultVMHelper? and does your View/DataTemplate has a Command Binding to your DialogViewmodel which raise the RequestCloseDialog?
eg
public class DialogViewmodel : INPCBase, IDialogResultVMHelper
{
private readonly Lazy<DelegateCommand> _acceptCommand;
public DialogViewmodel()
{
this._acceptCommand = new Lazy<DelegateCommand>(() => new DelegateCommand(() => InvokeRequestCloseDialog(new RequestCloseDialogEventArgs(true)), () => **Your Condition goes here**));
}
public event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e)
{
var handler = RequestCloseDialog;
if (handler != null)
handler(this, e);
}
}
anywhere in your Dialog control:
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" MinHeight="30">
<Button IsDefault="True" Content="Übernehmen" MinWidth="100" Command="{Binding AcceptCommand}"/>
<Button IsCancel="True" Content="Abbrechen" MinWidth="100"/>
</StackPanel>
and then your result should work in your viewmodel
var dialog = new DialogViewmodel();
var result = _dialogservice.ShowDialog("My Dialog", dialog );
if(result.HasValue && result.Value)
{
//accept true
}
else
{
//Cancel or false
}

Check PasswordBox if user type anything in WPF

I am using PasswordBox and I want to detect whenever the user typed there anything, if yes I need to change Button status to enabled. How can I check if user types anything
in the PasswordBox?
It behaves differently from TextBox since you can't bind it to text
and when user types anything raises some event. Any idea?
I have tried with the code below, but I get errors:
<PasswordBox>
<i:Interaction.Triggers>
<EventTrigger EventName="KeyDown">
<si:InvokeDataCommand Command="{Binding MyCommand}" />
</EventTrigger>
</i:Interaction.Triggers>
</PasswordBox>
You can use PasswordChanged event which fires when the string in the passwordbox changes:
XAML Part:
<PasswordBox Name="pwdBox" PasswordChanged="pwdBox_PasswordChanged" />
<Button Name="someButton" IsEnabled="False" Click="someClickEvent" />
C# Part:
private void pwdBox_PasswordChanged(object sender, RoutedEventArgs e)
{
somebutton.IsEnabled = String.IsNullOrWhiteSpace(pwdBox.Password));
}
Please note that MSDN says
When you get the Password property value, you expose the password as plain text in memory. To avoid this potential security risk, use the SecurePassword property to get the password as a SecureString.
Therefore the following code may be preferred:
private void pwdBox_PasswordChanged(object sender, RoutedEventArgs e)
{
btn.IsEnabled = pwdBox.SecurePassword.Length == 0;
}
If you only have access to viewModel, then you may use attached properties such that you create a bindable password or securepassword, as in this example
You can use the PasswordChanged event via Interactions like this:
XAML
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<PasswordBox BorderBrush="#FFB0B1AB"
Width="100"
Height="25"
VerticalAlignment="Bottom">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PasswordChanged">
<i:InvokeCommandAction Command="{Binding PasswordChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</PasswordBox>
RelayCommand
private ICommand _passwordChangedCommand = null;
public ICommand PasswordChangedCommand
{
get
{
if (_passwordChangedCommand == null)
{
_passwordChangedCommand = new RelayCommand(param => this.PasswordChanged(), null);
}
return _passwordChangedCommand;
}
}
private void PasswordChanged()
{
// your logic here
}
Some useful links
PasswordBox in WPF Tutorial
Binding to PasswordBox in WPF (using MVVM)
How to bind to a PasswordBox in MVVM

CaliburnMicro Action does not fire inside data template

I want to create some sort of filter, when user clicks the filter button from the app bar it will fire up a popup page with list picker in it. I've googled and tried quite a number of solutions but still cannot get it to work.
Here are my codes:
XAML (MainPageView.xaml)
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="PivotContentTemplate">
<phone:Pivot Margin="-12,0,0,0" Title="FOREX NEWS" Height="672">
<phone:PivotItem Header="filter" FontFamily="{StaticResource PhoneFontFamilySemiLight}" FontSize="32">
<StackPanel Margin="12,0,0,0">
<toolkit:ListPicker Header="currencies" SelectionMode="Multiple"
micro:Message.Attach="[Event SelectionChanged] = [Action OnCurrenciesChanged($eventArgs)]">
<sys:String>gbp</sys:String>
<sys:String>eur</sys:String>
<sys:String>usd</sys:String>
</toolkit:ListPicker>
</StackPanel>
</phone:PivotItem>
</phone:Pivot>
</DataTemplate>
<phone:PhoneApplicationPage.Resources>
...
Still inside MainPageView.xaml
<bab:BindableAppBar Grid.Row="2" Mode="Minimized">
<bab:BindableAppBarButton micro:Message.Attach="[Event Click] = [Action ShowFilter($view, $eventArgs]">
</bab:BindableAppBarButton>
</bab:BindableAppBar>
MainPageViewModel.cs
public void ShowFilter(object sender, RoutedEventArgs e)
{
var view= sender as MainPageView;
CustomMessageBox messageBox = new CustomMessageBox()
{
ContentTemplate = (DataTemplate)view.Resources["PivotContentTemplate"],
LeftButtonContent = "filter",
RightButtonContent = "cancel",
IsFullScreen = true // Pivots should always be full-screen.
};
messageBox.Dismissed += (s1, e1) =>
{
switch (e1.Result)
{
case CustomMessageBoxResult.LeftButton:
// Do something.
break;
case CustomMessageBoxResult.RightButton:
// Do something.
break;
case CustomMessageBoxResult.None:
// Do something.
break;
default:
break;
}
};
messageBox.Show();
}
public void OnCurrenciesChanged(SelectionChangedEventArgs e)
{
}
For your information, I am using Caliburn.Micro and WP Toolkit for the CustomMessageBox and ListPicker.
I received exception No target found for method OnCurrenciesChanged. I only receive the exception when I after I select few items in the list picker and click any of the buttons to save the change. Another thing is that the OnCurrenciesChanged does not get triggered at all.
I think (based on what I read so far) whenever the CustomMessageBox get called, the datacontext its operating at is no longer pointing to the MainPageViewModel thus it could not find the method. But I am not sure how to actually do this.
More details:
Exception happen after I click the left button (checkmark)
Updates
So far I have try the following:
<StackPanel Margin="12,0,0,0" DataContext="{Binding DataContext, RelativeSource={RelativeSource TemplatedParent}}"> //also tried with Self
and I also added this when I instantiate messageBox
var messageBox = new CustomMessageBox()
{
ContentTemplate = (DataTemplate)view.Resources["PivotContentTemplate"],
DataContext = view.DataContext, // added this
LeftButtonContent = "filter",
RightButtonContent = "cancel",
IsFullScreen = true
};
The idea is that when the messsagebox is created, the datacontext will be the same as when the view is instantiated. However, it seems that the datacontext does not get inherited by the PickerList
Ok so I managed to get this to work. The solution is not pretty and I think it beats the purpose of MVVM at the first place.
Based on http://wp.qmatteoq.com/first-steps-in-caliburn-micro-with-windows-phone-8-how-to-manage-different-datacontext/ , inside a template the DataContext will be different. So, I need to somehow tell ListPicker to use the correct DataContext.
The solution provided by link above doesn't work for me. I think it is because when ListPicker is called inside CustomMessageBox, MainPageViewModel is no longer available or it seems not to be able to find it as suggested by the exception. So as per above code example in the question, even if I set the correct DataContext to the CustomMessageBox, it does not get inherited somehow by the ListPicker.
Here is my solution:
var messageBox = new CustomMessageBox()
{
Name = "FilterCustomMessageBox", // added this
ContentTemplate = (DataTemplate)view.Resources["PivotContentTemplate"],
DataContext = view.DataContext,
LeftButtonContent = "filter",
RightButtonContent = "cancel",
IsFullScreen = true
};
In the XAML, I edited to this
<toolkit:ListPicker Header="currencies" SelectionMode="Multiple"
micro:Action.TargetWithoutContext="{Binding ElementName=FilterCustomMessageBox, Path=DataContext}"
micro:Message.Attach="[Event SelectionChanged] = [Action OnCurrenciesChanged($eventArgs)]">
It's ugly because both ViewModel and View need to explicitly know the Name. In WPF, you can just do something like this in the binding to inherit the DataContext of the parent/etc but this is not available for WP.
{Binding DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}
If anyone has better workaround, do let me know!

Categories