我在WPF中创建了一个自定义控件'Toast Notification',我想在MVVM中使用它来显示消息,但我不知道它在哪里实现它(MainWindow或每个页面)以及如何从每个页面发送消息to toast通知。请帮我解决这个问题。
public class Toast:Control
{
static Toast()
{
DefaultStyleKeyProperty.OverrideMetadata (typeof (Toast),new FrameworkPropertyMetadata (typeof (Toast)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate ();
ChangeVisualState ();
}
public static readonly DependencyProperty TextProperty=
DependencyProperty.Register ("Text",typeof (string),typeof (Toast),new PropertyMetadata ("Sample Text"));
public static readonly DependencyProperty ToastIconProperty=
DependencyProperty.Register ("ToastIcon",typeof (ToastIconType),typeof (Toast),new PropertyMetadata (ToastIconType.None,OnToastIconChanged));
public static readonly DependencyProperty IsToastVisibleProperty=
DependencyProperty.Register ("IsToastVisible",typeof (bool),typeof (Toast),new PropertyMetadata (false,OnIsToastVisibleChanged));
public static readonly DependencyProperty DurationProperty=
DependencyProperty.Register ("Duration",typeof (TimeSpan),typeof (Toast),new PropertyMetadata (TimeSpan.FromSeconds (10),OnDurationChanged));
public static readonly DependencyProperty ImageGeometryProperty=
DependencyProperty.Register ("ImageGeometry",typeof (Geometry),typeof (Toast));
public string Text
{
get { return (string)GetValue (TextProperty); }
set { SetValue (TextProperty,value); }
}
public bool IsToastVisible
{
get { return (bool)GetValue (IsToastVisibleProperty); }
set { SetValue (IsToastVisibleProperty,value); ChangeVisualState (); }
}
public ToastIconType ToastIcon
{
get { return (ToastIconType)GetValue (ToastIconProperty); }
set { SetValue (ToastIconProperty,value); }
}
public TimeSpan Duration
{
get { return (TimeSpan)GetValue (DurationProperty); }
set { SetValue (DurationProperty,value); }
}
public Geometry ImageGeometry
{
get { return (Geometry)GetValue (ImageGeometryProperty); }
set { SetValue (ImageGeometryProperty,value); }
}
private static void OnDurationChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
{
var control=(Toast)d;
var value=(TimeSpan)e.NewValue;
control.Duration=value;
}
private static void OnIsToastVisibleChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
{
var c=(Toast)d;
var value=(bool)e.NewValue;
c.IsToastVisible=value;
}
private void ChangeVisualState()
{
if (IsToastVisible)
{
DoubleAnimation da=new DoubleAnimation { From=1,To=0,Duration=TimeSpan.FromSeconds (Duration.Seconds) };
CubicEase cubicEase=new CubicEase ();
cubicEase.EasingMode=EasingMode.EaseInOut;
da.EasingFunction=cubicEase;
da.Completed+=(sender,e) => IsToastVisible=false;
BeginAnimation (OpacityProperty,da);
}
else
{
Opacity=0;
}
}
private static void OnToastIconChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
{
var control=(Toast)d;
var value=(ToastIconType)e.NewValue;
switch (value)
{
case ToastIconType.Information:
control.ImageGeometry=Geometry.Parse ("M3.6069946,1.8659973C2.6459963,1.8659973,1.8660278,2.6480103,1.8660278,3.6080017L1.8660278,20.546021C1.8660278,21.507019,2.6459963,22.288025,3.6069946,22.288025L11.647035,22.288025 9.6170056,27.481018 18.038026,22.288025 28.124027,22.288025C29.085025,22.288025,29.865054,21.507019,29.865054,20.546021L29.865054,3.6080017C29.865054,2.6480103,29.085025,1.8659973,28.124027,1.8659973z M3.6069946,0L28.124027,0C30.11304,0,31.731998,1.6190186,31.731998,3.6080017L31.731998,20.546021C31.731998,22.536011,30.11304,24.154022,28.124027,24.154022L18.567018,24.154022 5.8439948,32 8.9130261,24.154022 3.6069946,24.154022C1.618042,24.154022,-2.120687E-07,22.536011,0,20.546021L0,3.6080017C-2.120687E-07,1.6190186,1.618042,0,3.6069946,0z");
break;
case ToastIconType.Warning:
control.ImageGeometry=Geometry.Parse ("M13.950004,24.5L13.950004,28.299988 17.950004,28.299988 17.950004,24.5z M13.950004,10.399963L13.950004,21.699951 17.950004,21.699951 17.950004,10.399963z M15.950004,0C16.349998,0,16.750007,0.19995117,16.950004,0.69995117L31.750011,30.099976C31.949993,30.5 31.949993,31 31.750011,31.399963 31.549999,31.799988 31.150005,32 30.750011,32L1.1499981,32C0.75000406,32 0.34999478,31.799988 0.14999761,31.399963 -0.049999204,31 -0.049999204,30.5 0.14999761,30.099976L14.950004,0.69995117C15.150001,0.19995117,15.549995,0,15.950004,0z");
break;
}
}
public enum ToastIconType
{
None=0,
Information=1,
Warning=2
}
}
和我的吐司风格:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MetroCustomControls">
<Style TargetType="{x:Type local:Toast}">
<Style.Resources>
<SolidColorBrush x:Key="Toast.Static.Background" Color="LightGray" />
<SolidColorBrush x:Key="Toast.Static.BorderBrush" Color="Gray" />
<SolidColorBrush x:Key="Toast.Static.Foreground" Color="DimGray" />
</Style.Resources>
<Setter Property="Background" Value="{StaticResource Toast.Static.Background}" />
<Setter Property="BorderBrush" Value="{StaticResource Toast.Static.BorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{StaticResource Toast.Static.Foreground}" />
<Setter Property="FontFamily" Value="Segoe UI Light" />
<Setter Property="FontSize" Value="14" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,10" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Toast}">
<Grid x:Name="template_Root">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="2" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" Margin="0,0,0,20">
<StackPanel Orientation="Horizontal">
<Path x:Name="path" Margin="6,0,0,0" Fill="{TemplateBinding Foreground}" Stretch="Uniform" Width="12" Height="12" Data="{TemplateBinding ImageGeometry}" />
<TextBlock Text="{TemplateBinding Text}" Foreground="{TemplateBinding Foreground}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" Padding="5" />
</StackPanel>
</Border>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ToastIcon,RelativeSource={RelativeSource Self}}" Value="None">
<Setter TargetName="path" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
答案 0 :(得分:3)
这类似于MVVM中的Dialogs问题。
理想的解决方案是将功能包装在DialogService
或您的ToastService
中,您可以将其注入需要它的ViewModel中。
因此您的MainWindow或根ViewModel具有控件,并且能够显示Toasts。然后你有IToastService
看起来像这样:
public class ToastService
{
public event Action<String> ToastMessageRecieved;
public void ShowToast(string message)
{
ToastMessageRecieved(message);
}
}
ShowToast
操作的消费者(实际上显示吐司的内容),比如RootViewModel,可以订阅ToastMessageRecieved
操作:
public MyRootViewModel(ToastService toastService)
{
//keep it as a dependency in case we want to show toasts
this.toastService = toastService;
toastService.ToastMessageRecieved += (message) =>
{
//here's where you actually show your toast, however that's done
//MyRootViewModel has the actual UI element reference. It only
//appears in this one place.
Toast.Message = message;
};
}
任何其他想要显示Toast的ViewModel都会使用该服务:
public MyRandomToastGeneratingViewModel(ToastService toastService)
{
//our service is inejcted as a dependency
this.toastService = toastService;
}
public void ShowAToastButtonPressed()
{
toastService.ShowToast("My great toast!");
}
理想情况下,您可以使用某种依赖注入框架 - 但它值得为此类事情实施。对话服务,例如你的祝酒词,工厂和数据访问都适合MVVM和DI。
要将实际控件(您拥有的单个实例)放在其他所有内容之上,在Window或RootView中,您可以让它占用与普通内容相同的网格单元格。这就是我通常做重叠控件的方法:
<Window x:Class="MyApp.MainWindow"
...>
<Grid>
//my normal content
<myControls:Toast VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="20"/>
</Grid>
</Window>
按照添加顺序,它们将占据相同的空间。