First of I should apologize if the question seems to look simple however I'm new to WPF and MVVM and I really don't know how to do the job.
What I need is to show popups from ViewModel. I know I can have a boolean property in the viewModel and bind it to the IsOpen property of the Popup but I don't know where to create this popup window.
I have some views and each view has to display certain popup messages depending on different situations. Now I need to know whether I should create several popups in each view and bind their IsOpen property to that of in the ViewModel or there are better solutions, and if I should create them in the view, where to put them? In a grid, in a StackPanel or anywhere else.
please let me know if I haven't explained clearly. Any help is appreciated.
I usually have a Third object to control my popup's and dialog , like Caliburn's WindowManager
WindowManager
Witch takes a ViewModel as Content and Displays it's Corresponding View in the Popup.
You can do something similar and Bind a Content from your ViewModel to your popup or Dialog.
For Instance , here's an a Custom Action i created for such a purpose :
OpenPopupWindowAction
this is sample popup. reference
basic popup msdn overview
<StackPanel>
<CheckBox Name="PCheckBox" Margin="10,10,0,0"
Content="Popup Window"/>
<Button HorizontalAlignment="Left" Width="129" Margin="10,10,0,0">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="theTransform"
Storyboard.TargetProperty="(RotateTransform.Angle)"
From="0" To="360" Duration="0:0:5" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
Start Animation
</Button>
<Popup IsOpen="{Binding ElementName=PCheckBox,Path=IsChecked}"
PlacementTarget="{Binding ElementName=PCheckBox}"
AllowsTransparency="True"
PopupAnimation="Slide"
HorizontalOffset="150"
VerticalOffset="100"
>
<Canvas Width="100" Height="100" Background="Green" Margin="150">
<Canvas.RenderTransform>
<RotateTransform x:Name="theTransform" />
</Canvas.RenderTransform>
<TextBlock TextWrapping="Wrap" Foreground="LightGray">
Rotating Popup
</TextBlock>
</Canvas>
</Popup>
private void OnPopupLoaded(object sender, RoutedEventArgs e)
{
this.ParentPopup.HorizontalOffset = (Window.Current.Bounds.Width - gdChild.ActualWidth) / 2;
this.ParentPopup.VerticalOffset = (Window.Current.Bounds.Height - gdChild.ActualHeight) / 2;
}
Related
My requirement is to control the closing/opening of popup from a Button. In my below piece of code by clicking on button the popup opens up but it doesn't closes on the next click.
XAML
<Button x:Name="Btn" Height="20" Width="120" HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="Show Popup"/>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="Popup"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Popup x:Name="Popup" StaysOpen="False"
Height="100" Width="300" Background= "Yellow"
PlacementTarget="{Binding ElementName=Btn}"></Popup>
Note: I am able to achieve the above case using a Toggle button, But my requirement is very specific to button only.
You can't do it without toggling i.e. Button alone can't do it. You are handling a control that has a state: opened and closed. You need to know the current state. To achieve this, add a flag to the hosting control (requires code-behind) or use the flag of the Popup (requires code-behind) or use the flag of the ToggleButton (no code-behind).
It doesn't make any sense to avoid the ToggleButton: it's a Button that additionally knows or has a state. It can handle ICommand or emit a Click event - it's a Button. It's exactly what you need.
You don't have to explicitly add a TextBlock to the Content of the Button. When assigning a string to Button.Content, the control will automatically create a TextBlock to show the string.
MainWindow.xaml
<Button Click="TogglePopup_OnClick"
Content="Show Popup" />
<Popup x:Name="Popup" />
MainWindow.xaml.cs
private void TogglePopup_OnClick(object sender, EventArgs e)
=> this.Popup.IsOpen ^= true;
I tried to find answers to my question on the internet, but no answer satisfied me enough. I am writing WPF application and i'm trying to implement dialog mechanism. I have a simple ViewModel, and when some event happens, i would like to show a dialog, collect some output data from it and store it in "parent" View Model.
My method in a view model looks like this:
private void Expand()
{
...
catch(ArgumentNullException)
{
Shrink();
var errorDialogVM = new DialogVM(new Dialog() { Type = DialogType.Error, Message = $"Unauthorized access to \"{FileManager.GetDirectoryName(Path)}\" directory!" });
DialogService.ShowDialog(errorDialogVM);
//Here i need output from dialog
}
}
Implementation of ShowDialog method:
public void ShowDialog(DialogVM dialogVM)
{
var dialog = new DialogBox();
var mainWindow = Application.Current.MainWindow as MainWindow;
dialog.DataContext = dialogVM;
dialog.Owner = mainWindow;
dialog.Show();
}
Now, let's imagine that i need some data from the dialog. How can i pass it to my ViewModel in a proper way?
View model shouldn't handle view elements. A dialog is a view element.
The view model can trigger user input by raising and event e.g., an error event with an data model as event args. The view that has registered to the event shows a dialog to collect user input and stores them in the previously received data model. The view then executes a command on the view model to pass back the data model.
Instead of an event you can also bind the view to a property of the view model e.g. of type bool. On property change show the dialog and return the result using a ICommand.
Alternatively let the view model expose a flag e.g. HasException and a property ExceptionDialogModel which can be used to bind a custom dialog or form. Then create a simple modal dialog yourself:
ExampleDialog
<Grid x:Name="ExampleDialog"
Visibility="Visible"
Panel.ZIndex="100"
VerticalAlignment="Top">
<Rectangle Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=ActualHeight}"
Fill="Gray"
Opacity="0.7" />
<Grid Width="400"
Height="200">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Border Grid.RowSpan="2"
Background="LightGray"
BorderBrush="Black"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect BlurRadius="5"
Color="Black"
Opacity="0.6" />
</Border.Effect>
</Border>
<TextBlock Grid.Row="0"
TextWrapping="Wrap"
Margin="30"
Text="I am a modal dialog and my Visibility or Opacity property can be easily modified by a trigger or a nice animation" />
<StackPanel Orientation="Horizontal"
Grid.Row="1"
HorizontalAlignment="Right"
Height="50">
<Button x:Name="OkButton"
Content="Ok"
Width="80" />
<Button x:Name="CancelButton"
Margin="30,0,30,0"
Content="Cancel"
Width="80" />
</StackPanel>
</Grid>
<Grid.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExampleDialog"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
</Grid>
You can put the Grid anywhere in your Window and toggle the Visibility. It will overlay the parent Window and has modal behavior.
Bind the DataContext to the ExceptionDialogModel so that the data is send back via TwoWay binding. Use a command to trigger a retry procedure (e.g., an OK or Retry button).
The Visibility can bind to the HasException property. You can animate this dialog and give it any look and feel you like.
I believe you are doing this backwards. You should pass reference to your view model to the dialog, not the other way around because view model should be separate and not aware of the view's mechanics. Dialog, on the other hand, knows which properties of view model it needs to set. So it will be something like the following:
public class MyDialog : Dialog
{
public MyDialog(DialogVM ViewModel) {
this.InitializeComponent();
this.DataContex = ViewModel;
// TODO: Bind to view model's properties in XAML or set them on OnClose()
}
}
I'm developing universal app. On one page i decided to use FlipView. I can easily animate SelectionChanged event from code-behind, but i'm just curious if there is a way to animate this event using XAML only. (BTW, UseTouchAnimationsForAllNavigation="True" doesnt work).
So, here's simplified example of what i'm doing :
<FlipView x:Name="MultipleItems">
<FlipView.Triggers>
<EventTrigger RoutedEvent="Selector.SelectionChanged">
<BeginStoryboard>
<Storyboard x:Name="ColorStoryboard">
//do stuff
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<FlipView.Triggers>
</FlipView>
I think this way of usage EventTrigger is fine (as far as SelectionChanged event takes arguments inherited from RoutedEventArgs), but it still gives me runtime error on navigation to page that contains FlipView.
Error is next :
WinRT information: Failed to assign to property 'Windows.UI.Xaml.EventTrigger.RoutedEvent'. [Line: 69 Position: 35]
Additional information: The text associated with this error code could not be found.
I believe there's way to assign that RoutedEvent property correctly, but i didnt find it yet. Also I don't wont to use behaviours for such simple thing.
Can anyone help?
You need to install the Microsoft.Xaml.Behaviors.Uwp.Managed in your project. Then the EventTrigger will be supported in an UWP project.
Then in your XAML use this package like this:
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Media="using:Microsoft.Xaml.Interactions.Media"
Now you can for example change the background color of FlipView like this:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<Storyboard x:Key="std" x:Name="std" >
<ColorAnimation From="Red" To="Transparent" Duration="0:0:3"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
Storyboard.TargetName="flipView"/>
</Storyboard>
</Grid.Resources>
<FlipView x:Name="flipView" ItemsSource="{x:Bind flipviewCollection}">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="SelectionChanged">
<Media:ControlStoryboardAction Storyboard="{StaticResource std}" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<FlipView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImageSource}" Stretch="None"/>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Grid>
As you can see, I used EventTriggerBehavior and the event's name is SelectionChanged.
I have some image in my project. I want to change its property Visibility to true when the button pressed. I've created an image
<Image Name="UserimgRock" Source="Rock.png" HorizontalAlignment="Left" Height="100" Margin="277" VerticalAlignment="Top" Width="100" Visibility="Hidden"/>
and button
private void btnRock_Click(object sender, RoutedEventArgs e)
{
UserimgRock.Visibility = Visibility.Visible;
}
but there is an error Error *The name 'UserimgRock' does not exist in the current context. I'm a little confused.
Thanks for a any help!
if these both xaml and code behind belongs to same class then just prefix Name with x: where x: is referring to xaml name space, usually in Window tag like xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
eg
<Image x:Name="UserimgRock" Source="Rock.png" HorizontalAlignment="Left" Height="100" Margin="277" VerticalAlignment="Top" Width="100" Visibility="Hidden"/>
if these both are not in the same class then you may not be able to use the event handler for accessing objects in other class as objects are private to the respective class.
Edit
after looking at your code I discovered a few things including the reason why you cannot access the image
first things first, why you cannot access the image object?
reason is, you've defined the image inside a control template which limits the scope of the objects within to the template itself, so they are not accessible outside
how to fix?
you can define the property you need to access in the code behind and bind them to the respective properties in xaml
eg
define a dependency property UserImgRockVisibility in the usercontrol
public Visibility UserImgRockVisibility
{
get { return (Visibility)GetValue(UserImgRockVisibilityProperty); }
set { SetValue(UserImgRockVisibilityProperty, value); }
}
// Using a DependencyProperty as the backing store for UserImgRockVisibility. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UserImgRockVisibilityProperty =
DependencyProperty.Register("UserImgRockVisibility", typeof(Visibility), typeof(UserControl1), new PropertyMetadata(Visibility.Hidden));
set the data context to itself in constructor
DataContext = this;
or in xaml via binding to self
<UserControl x:Class="RockPaper.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
then use this property to manipulate the visibility
UserImgRockVisibility = Visibility.Visible;
finally bind this property to visibility of the image in xaml
<Image x:Name="userimgRock" Visibility="{Binding UserImgRockVisibility}" ... />
name is not necessary if you do not really need it
this is all you need in order to control the property of a object inside a control template or data template
another approach
Since I do not find a significance of using a style and control template within the user control
you can do it by simply removing the style and control template elements and bring the elements to usercontrol
by doing this you may not require the extra properties you may simply access the objects as they will be in the class scope
All I can see that you've used them for mouse over detection, you can use event triggers in elements directly using MouseEnter, MouseLeave kind of events
eg,
<UserControl x:Class="RockPaper.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button x:Name="btnRock" Opacity=".3" HorizontalAlignment="Left" Margin="44,82.418,0,0" VerticalAlignment="Top" Width="100" Height="100" Click="btnRock_Click">
<StackPanel>
<Image />
</StackPanel>
</Button>
<Image x:Name="userimgRock" Visibility="Hidden" HorizontalAlignment="Left" Height="100" Margin="277.164,228.418,0,0" VerticalAlignment="Top" Width="100" Opacity="1" />
</Grid>
<UserControl.Triggers>
<EventTrigger SourceName="btnRock" RoutedEvent="MouseEnter" >
<BeginStoryboard>
<Storyboard>
<DoubleAnimation x:Name="enter" To="1" Storyboard.TargetName="btnRock" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger SourceName="btnRock" RoutedEvent="MouseLeave" >
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To=".3" Storyboard.TargetName="btnRock" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</UserControl.Triggers>
</UserControl>
Note I stripped some of the extra code not necessary for example
then use image object normally as
userimgRock.Visibility = Visibility.Visible;
now the choice is yours to choose what you prefer. do try both and see what is convenient to you.
I have WP7 application with simple phone page.
I have check box
<CheckBox Content="Click me" Margin="0,2,0,0">
When I check the checkbox I want Stack panel below it to expand.
<StackPanel Height="0" x:Name="MyStackPanel">
<CheckBox Content="Condition"/>
</StackPanel>
Right now I tried this solution, but i receive exception that EventTrigger.RoutedEvent cannot be assign to Checkbox.Checked.
<CheckBox Content="Click Me" Margin="0,2,0,0">
<CheckBox.Triggers>
<EventTrigger RoutedEvent="CheckBox.Checked">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="MyStackPanel"
Storyboard.TargetProperty="Height"
To="100"
Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</CheckBox.Triggers>
</CheckBox>
Do you have any ideas what is the best approach to implement such animation?
Thanks
There is a control for what you require to do. You should be using the Expander control that iss part of the silverlight toolkit . There iS A good two part tutorial about working with this control here on WindowsPhoneGeek.
Basically, you use the expander control and implement your own custom controls for the header and the items.
try ToggleButton.Checked instead of CheckBox.Checked