MVVM, DialogService and Dialog Result - c#

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
}

Related

WPF Update UI From Background Thread

I know similar questions have been asked many times but I have not been able to find anything that works in this situation. I have an application that runs minimized to the taskbar using wpf-notifyicon. The main window opens, authenticates and is then hidden. A process runs in the background and I would like it to send updates to the main thread. Most of the time it works. However, the taskbar icon has a context menu that allows the user to open application settings. If I open then close the settings window, the next time I try to update the balloon, I get a a null reference error System.NullReferenceException: 'Object reference not set to an instance of an object.' System.Windows.Application.MainWindow.get returned null.
It's like once I open another window, the main window is lost and I can't figure out how to find it again.
This is how I am updating the balloon. This code is in a notification service and is called from inside the MainWindow View Model and from inside other services.
// Show balloon update on the main thread
Application.Current.Dispatcher.Invoke( new Action( () =>
{
var notifyIcon = ( TaskbarIcon )Application.Current.MainWindow.FindName( "NotifyIcon" );
notifyIcon.ShowBalloonTip( title, message, balloonIcon );
} ), DispatcherPriority.Normal );
The notification icon is declared inside the XAML for the main window.
<tb:TaskbarIcon
x:Name="NotifyIcon"
IconSource="/Resources/Icons/card_16x16.ico"
ToolTipText="Processor"
MenuActivation="LeftOrRightClick"
DoubleClickCommand="{Binding ShowStatusCommand}">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="Settings" Command="{Binding ShowSettingsCommand}" />
<Separator />
<MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
<tb:TaskbarIcon.TrayToolTip>
<Border Background="Gray"
BorderBrush="DarkGray"
BorderThickness="1"
CornerRadius="3"
Opacity="0.8"
Width="180"
Height="20">
<TextBlock Text="{Binding ListeningMessage }" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</tb:TaskbarIcon.TrayToolTip>
</tb:TaskbarIcon>
How can I safely update the balloon icon from background threads?
Update 1:
The context menu is bound to commands in the view model. To open the settings window
<ContextMenu>
<MenuItem Header="Settings" Command="{Binding ShowSettingsCommand}" />
<Separator />
<MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}" />
</ContextMenu>
The command in the VM is:
public ICommand ShowSettingsCommand => new DelegateCommand
{
CommandAction = () =>
{
Application.Current.MainWindow = new Views.SettingsWindow( _logger, _hidservice, _certificateService );
Application.Current.MainWindow.Show();
}
};
To close the settings window, I have an action in the window code behind
public ICommand CancelSettingsCommand => new DelegateCommand
{
CommandAction = () => CloseAction()
};
// In Code Behind
vm.CloseAction = new Action( () => this.Close() );
You are overriden the main window. You should not do that. Just create a new instance of it and call Show().
public ICommand ShowSettingsCommand => new DelegateCommand
{
CommandAction = () =>
{
var settingsWindow = = new Views.SettingsWindow( _logger, _hidservice, _certificateService );
settingsWindow.Show();
}
};
Don't show the window from the view model. Use an event handler instead.
Also don't override the value of Application.MainWindow.
Don't use Dispatcher to show progress. Since .NET 4.5 the recommended pattern is to use IProgres<T>. The frameworks provides a default implementation Progress<T>:
Progress model to hold progress data
class ProgressArgs
{
public string Title { get; set; }
public string Message { get; set; }
public object Icon { get; set; }
}
Main UI thread
private void ShowSettings_OnMenuClicked(object sender, EventArgs e)
{
// Pass this IProgress<T> instance to every class/thread
// that needs to report progress (execute the delegate)
IProgres<ProgressArgs> progressReporter = new Progress<ProgressArgs>(ReportPropgressDelegate);
var settingsWindow = new Views.SettingsWindow(_logger, _hidservice, _certificateService, progressReporter);
}
private void ReportPropgressDelegate(ProgressArgs progress)
{
var notifyIcon = (TaskbarIcon) Application.Current.MainWindow.FindName("NotifyIcon");
notifyIcon.ShowBalloonTip(progress.Title, progress.Message, progress.Icon);
}
Background thread
private void DoWork(IProgress<ProgressArgs> progressReporter)
{
// Do work
// Report progress
var progress = new ProgressArgs() { Title = "Title", Message = "Some message", Icon = null };
progressReporter.Report(progress);
}

Caliburn Micro void function can't be triggered inside DialogHost

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

How to update content of dialoghost in wpf material design?

This error happen when I open new usercontrol into dialoghost during opening dialoghost :
when I press submit button
this is my code xaml:
MainWindow:
<Grid>
<Button Content="Show"
Command="{Binding OpenRegisCommand}"
VerticalAlignment="Bottom"
Margin="0 0 0 40"
Foreground="White"
Width="100"
Background="#23A9F2">
</Button>
<md:DialogHost Identifier="RootDialogHostId">
</md:DialogHost>
</Grid>
my usercontrol and my mainwindow using 1 viewmodel:
public MainViewModel()
{
OpenRegisCommand = new DelegateCommand(OpenFormRegis);
Submit = new DelegateCommand(Check);
}
private async void Check()
{
if(Name.Equals("admin")&&Pass.Equals("123456"))
{
var view = new DialogOk();
await DialogHost.Show(view, DialogHostId);
}
else
{
var view = new DialogNo();
await DialogHost.Show(view, DialogHostId);
}
}
private async void OpenFormRegis()
{
var view = new FormRegis();
await DialogHost.Show(view, DialogHostId);
}
button submit in my usercontrol binding to DelegateCommand Submit in my viewmodel
Each dialog hosts can be used to show one view at a time.
If you have two views that you wanted to shot at the same time (less likely), you will need two dialog hosts.
In your case, if you're trying to open the "OpenFormRegis" window in your dialog host, I would suggest you to use Windows instead.
In my case, I did the following:
1- Get the dialogs that are active, using DialogHost.GetDialogSession("RootDialog").
2- If it is different from null, set the content with the UpdateContent(alertBox) method which updates the content of the dialog.
3- If it is null, establish the usual flow to show the content of the dialog.
AlertBoxView alertBox = new AlertBoxView();
alertBox.DataContext = this;
var dialog = DialogHost.GetDialogSession(IdentifierDialog);
if (dialog != null)
{
dialog.UpdateContent(alertBox);
}
else
{
await DialogHost.Show(alertBox, IdentifierDialog);
}
*AlertBoxView: is my custom view of DialogHost
*IdentifierDialog: is the variable that takes the Identifier of the dialog

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.

Changing isVisible property of Xamarin Forms XAML buttons

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;
}

Categories