MAUI binding issue - .net-maui

I have a MAUI mobile app that uses CommunityToolkit.Mvvm. On a page, there is a ListView:
<ListView
Grid.Row="0"
ItemsSource="{Binding Features}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodel:FeatureListItem">
<ViewCell>
<Grid
RowDefinitions="*,1"
ColumnDefinitions="10*,*">
<Label x:Name="txtDescription" Text="{Binding DescriptionText}" BackgroundColor="Transparent" TextColor="Black" Grid.Row="0" Grid.Column="0" />
<Label x:Name="txtIcon" Text="{Binding IconText}" BackgroundColor="Transparent" TextColor="Black" Style="{StaticResource ToolbarItemIconStyle}" Grid.Row="0" Grid.Column="1" />
...
Features is a list of FeatureListItem:
public class FeatureListItem
{
public string DescriptionText { get; set; }
public string IconText { get; set; }
public IRelayCommand Command { get; set; }
public FeatureListItem(string descriptionText, string iconText, IRelayCommand command)
{
DescriptionText = descriptionText;
IconText = iconText;
Command = command;
}
}
In the viewmodel constructor, the Features list is initialized :
Features = new List<FeatureListItem>
{
new FeatureListItem(ToggleLoginText, ToggleLoginIcon, ToggleLoginCommand),
...
};
IsLoggedIn = App.UserContext?.IsLoggedOn ?? false;
ToggleLoginIcon = IsLoggedIn ? MaterialDesignFontHelper.Logout : MaterialDesignFontHelper.Login;
ToggleLoginText = IsLoggedIn ? _textSignOut : _textSignIn;
Properties used in the code above:
[ObservableProperty]
private List<FeatureListItem> _features;
[ObservableProperty]
private string _toggleLoginIcon = MaterialDesignFontHelper.Login;
private const string _textSignIn = "Sign In";
private const string _textSignOut = "Sign Out";
[ObservableProperty]
private string _toggleLoginText = _textSignIn;
The problem is that when ToggleLoginText and ToggleLoginIcon are changed programmatically, txtDescription and txtIcon in the UI do not change. I guess, it is because DescriptionText and IconText in FeatureListItem are not observable.
I tried to derive FeatureListItem from ObservableObject and make the properties observable:
public partial class FeatureListItem : ObservableObject
{
[ObservableProperty]
private string _descriptionText;
[ObservableProperty]
private string _iconText;
But it did not help. What am I missing?

As #ToomakerSteve mentioned in his comment, I was observing and changing wrong properties. As _toggleLoginIcon and _toggleLoginText are used only once to initialize FeatureListItem with its properties, no need to observe or change them. Instead, I had to change properties of FeatureListItem _descriptionText and _iconText:
FeatureListItem loginFeature = Features.FirstOrDefault(); // Login feature is the first element in the list
loginFeature.IconText = IsLoggedIn ? MaterialDesignFontHelper.Logout : MaterialDesignFontHelper.Login;
loginFeature.DescriptionText = IsLoggedIn ? _textSignOut : _textSignIn;
This way the UI is updated properly.

Related

Display a popup with xamarin forms

I want to display a small popup in a xamarin forms application (iOS/Android PCL)
I am actually working with a ContentPage (XAML + C# code)
and i am showing this popup with:
await Navigation.PushModalAsync(mypopupinstance)
It works fine, but the popup is on the full screen. I just want a small popup and i want to see what is behind.
Thanks
You'll need to look elsewhere for this kind of functionality. One such library is Rotorgames' Popup plugin: https://github.com/rotorgames/Rg.Plugins.Popup
Modal pages can't be presented like that.
For small popups you can use
DisplayAlert()
Inside a Page.
If you want something more customizable just wrap the content of your Page Inside a relative Layout or grid, and add the popup on top of your normal content.
I am working on the same issue, so far I was able to create a popup which can hold a content page.
I am gladly willing to share my current state. Please note that I will keep the code examples as short as possible, therefore reducing it to a simple loading and a text prompt dialog.
Approach
After having used the Acr.UserDialogs library for a while, i felt the need of having dialogs I could customize due to my personal requirements. Also I wanted to minimize the necessity of having to rely on plugins.
Ideally such a dialog should be invoked with a simple call, for instance:
Dialogs.ShowLoading();
or
string result = Dialogs.ShowPrompt();
As things are with Xamarin.Forms it is quite obvious that we will require a dependency service implementation for that to work.
Shared Code Library
We create a basic interface "IDialogs.cs":
public interface IDialogs
{
bool IsDialogOpen();
void ShowLoading(LoadingDialog dialog);
void ShowPrompt(PromptDialog dialog);
void HideDialog();
}
Next thing to do is to have a static dialog class, which can be called from every page where a dialog is needed.
"Dialogs.cs":
public static class Dialogs
{
private static IDialogs dialogService = DependencyService.Get<IDialogs>();
public static bool IsDialogOpen()
{
return dialogService.IsDialogOpen();
}
public static void ShowLoading()
{
LoadingDialog dlg = new LoadingDialog();
dialogService.ShowLoading(dlg);
}
public static Task<string> ShowPromptText()
{
TaskCompletionSource<string> dialogCompletion = new TaskCompletionSource<string>();
PromptDialog dialog = new PromptDialog();
dialog.Canceled += (object sender, object result) => { dialogService.HideDialog(); dialogCompletion.SetResult((string)result); };
dialog.Confirmed += (object sender, object result) => { dialogService.HideDialog(); dialogCompletion.SetResult((string)result); };
dialogService.ShowPrompt(dialog);
return dialogCompletion.Task;
}
public static void HideDialog()
{
dialogService.HideDialog();
}
}
You will notice, that we are using a TaskCompletionSource together with custom event handlers in the ShowPromptText method. This enables us to display the dialog and await the user pressing the okay or cancel button and consuming the returned result.
As of now, the dialogs are as simple as they can get. I use some additional code for styling and themeing, but i will leave that out in order to keep this answer short and simple.
The loading dialog:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Dialogs.LoadingDialog" BackgroundColor="Transparent">
<ContentPage.Content>
<Grid BackgroundColor="#bb000000">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Frame BackgroundColor="Black" CornerRadius="15" Grid.Row="1" x:Name="ContentGrid" Margin="100,0,100,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ActivityIndicator Grid.Row="0" Color="White" IsRunning="True" HorizontalOptions="Center" VerticalOptions="Center"/>
<Label x:Name="LoadingLabel" Text="Loading ..." VerticalOptions="End" HorizontalOptions="Center" Grid.Row="1" TextColor="White" />
</Grid>
</Frame>
</Grid>
</ContentPage.Content>
(no need to post xaml.cs code for this, since there is no interaction with a loading screen)
The prompt dialog
PromptDialog.Xaml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dialogs="clr-namespace:BetterUI.Dialogs"
x:Class="MyApp.Dialogs.PromptDialog" BackgroundColor="Transparent">
<ContentPage.Content>
<ScrollView>
<Grid BackgroundColor="#bb000000">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Frame x:Name="ContentGrid" Grid.Row="1" CornerRadius="15" BackgroundColor="White" Margin="50,0,50,0" Padding="0">
<Grid Grid.Row="1" Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid x:Name="HeadingGrid" Padding="10, 5, 10, 5" Margin="-15,0,-15,0" Grid.Row="0" BackgroundColor="Black">
<Label x:Name="HeadingLabel" Text="Enter text" TextColor="White" Margin="20,0,20,0"/>
</Grid>
<Label l x:Name="DescriptionLabel" Text="Enter your text" Grid.Row="1" Margin="15,0,15,0"/>
<Entry x:Name="DialogResultText" Placeholder="Text" PlaceholderColor="LightGray" TextColor="Black" Grid.Row="2" Margin="15,0,15,0"/>
<Grid Grid.Row="3" ColumnSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="CancelButton" Text="Cancel" Clicked="OnCancelClick" Grid.Column="0" CornerRadius="0"/>
<Button x:Name="ConfirmButton" Text="Okay" Clicked="OnOkayClick" Grid.Column="1" CornerRadius="0"/>
</Grid>
</Grid>
</Frame>
</Grid>
</ScrollView>
</ContentPage.Content>
PromptDialog.xaml.cs:
public partial class PromptDialog : ContentPage
{
public event EventHandler<object> Confirmed;
public event EventHandler<object> Canceled;
public PromptDialog()
{
InitializeComponent();
}
private void OnCancelClick(object sender, EventArgs e)
{
Canceled?.Invoke(this, string.Empty);
}
private void OnOkayClick(object sender, EventArgs e)
{
Confirmed?.Invoke(this, DialogResultText.Text);
}
}
Android implementation
First of all we will create the android implementation of our IDialogs interface created in the shared code earlier:
[assembly: Dependency(typeof(DialogService))]
namespace MyApp.Droid.Services
{
/// <summary>
/// Handles displaying dialog items on screen
/// </summary>
public class DialogService : IDialogs
{
private static DialogFragment currentDialog;
/// <summary>
/// returns if a dialog is already open
/// </summary>
/// <returns></returns>
public bool IsDialogOpen()
{
return (currentDialog != null && currentDialog.IsVisible);
}
/// <summary>
/// Initialize Dialog Service with activity
/// </summary>
/// <param name="activity">activity</param>
public static void Init(Activity activity)
{
Activity = activity;
}
public static Activity Activity { get; set; }
/// <summary>
/// Displays a loading dialog
/// </summary>
/// <param name="dialog">Instance of progress dialog (xamarin.forms)</param>
public void ShowLoading(Dialogs.LoadingDialog dialog)
{
if (Activity == null)
return;
DialogFragment frag = dialog.CreateDialogFragment(Activity);
frag.SetStyle(DialogFragmentStyle.NoTitle, Resource.Style.DialogFrame);
frag.Show(Activity.FragmentManager, "dialog");
currentDialog = frag;
}
/// <summary>
/// Displays a prompt dialog
/// </summary>
/// <param name="dialog"></param>
public void ShowPrompt(Dialogs.PromptDialog dialog)
{
if (Activity == null)
return;
DialogFragment frag = dialog.CreateDialogFragment(Activity);
frag.SetStyle(DialogFragmentStyle.NoTitle, Resource.Style.DialogFrame);
frag.Show(Activity.FragmentManager, "dialog");
currentDialog = frag;
}
/// <summary>
/// Hides loading dialog
/// </summary>
public void HideDialog()
{
if (Activity == null)
return;
if (currentDialog != null)
{
currentDialog.Dismiss();
currentDialog = null;
}
}
}
}
Note that you have to set the activity for the dialog service prior to calling the actual methods to show a dialog, so in your MainActivity.cs make sure to call
DialogService.Init(this);
after having initialized Xamarin.Forms.
Finally, here comes some black magic:
Usually, one would implement such a dialog in Android by putting a fragment container into the main layout and throwing a fragment inside. Unfortunately, due to using Xamarin.Forms, such a main layout isn't available by default.
Even though Xamarin.Forms offers a view extension, which allows converting a ContentPage to a fragment (ContentPage.CreateFragment), we won't have any success of using this because it needs a target fragment container to be placed in.
However, android provides something called a DialogFragment, which can be thrown at the screen without having the need of a defined fragment container.
Unfortunately there isn't any out-of-the-box solution for creating a DialogFragment from Xamarin Forms. Good news is that this can be overcome with the power of using System.Reflection(;), so we create our own extension method and some modified versions of internal classes, xamarin.forms uses under the hood. In order to achieve this, I took the code from Xamarin.Forms in Xamarin.Platform.Android and modified it to create a DialogFragment from a ContentPage:
public static class PageExtensions
{
public static DialogFragment CreateDialogFragment(this ContentPage view, Context context)
{
if (!Forms.IsInitialized)
throw new InvalidOperationException("call Forms.Init() before this");
// Get Platform constructor via reflection and call it to create new platform object
Platform platform = (Platform)typeof(Platform).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(Context), typeof(bool) }, null)
?.Invoke(new object[] { context, true });
// Set the page to the platform
if (platform != null)
{
platform.GetType().GetMethod("SetPage", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(platform, new object[] { view });
// Finally get the view group
ViewGroup vg = (Android.Views.ViewGroup)platform.GetType().GetMethod("GetViewGroup", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(platform, null);
return new EmbeddedDialogFragment(vg, platform);
}
return null;
}
public class DefaultApplication : Xamarin.Forms.Application
{
}
class EmbeddedDialogFragment : DialogFragment
{
readonly ViewGroup _content;
readonly Platform _platform;
bool _disposed;
public EmbeddedDialogFragment()
{
}
public EmbeddedDialogFragment(ViewGroup content, Platform platform)
{
_content = content;
_platform = platform;
}
public override global::Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
this.Dialog.Window.SetSoftInputMode(SoftInput.AdjustResize);
return _content;
}
public override void OnDestroy()
{
this.Dialog?.Window.SetSoftInputMode(SoftInput.AdjustPan);
base.OnDestroy();
}
protected override void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_disposed = true;
if (disposing)
{
(_platform as IDisposable)?.Dispose();
}
base.Dispose(disposing);
}
}
}
iOS implementation
Fortunately, for the iOS implementation no deep diving into Xamarin.Forms code is necessary:
Here is the iOS implementation of DialogService:
[assembly: Dependency(typeof(DialogService))]
namespace BetterUI.iOS.Services
{
public class DialogService : IDialogs
{
private UIViewController currentDialog;
private UIWindow popupWindow = null;
public void HideLoading()
{
if (currentDialog != null)
{
UIApplication.SharedApplication.KeyWindow.RootViewController.DismissModalViewController(false);
currentDialog.Dispose();
currentDialog = null;
}
}
public bool IsDialogOpen()
{
return (currentDialog != null && currentDialog.IsBeingPresented);
}
public void ShowLoading(LoadingDialog dialog)
{
UIViewController dialogController = dialog.CreateViewController();
ShowDialog(dialogController);
currentDialog = dialogController;
}
public void ShowPrompt(PromptDialog dialog)
{
UIViewController dialogController = dialog.CreateViewController();
ShowDialog(dialogController);
currentDialog = dialogController;
}
private void ShowDialog(UIViewController dialogController)
{
var bounds = UIScreen.MainScreen.Bounds;
dialogController.View.Frame = bounds;
UIApplication.SharedApplication.KeyWindow.RootViewController.ModalPresentationStyle = UIModalPresentationStyle.CurrentContext;
UIApplication.SharedApplication.KeyWindow.RootViewController.AddChildViewController(dialogController);
UIApplication.SharedApplication.KeyWindow.RootViewController.View.Opaque = false;
UIApplication.SharedApplication.KeyWindow.RootViewController.View.Layer.AllowsGroupOpacity = true;
UIApplication.SharedApplication.KeyWindow.RootViewController.View.Layer.BackgroundColor = new CGColor(Color.White.ToCGColor(), 0.0f);
UIApplication.SharedApplication.KeyWindow.RootViewController.View.BackgroundColor = UIColor.Clear;
UIApplication.SharedApplication.KeyWindow.RootViewController.View.AddSubview(dialogController.View);
dialogController.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
dialogController.View.Opaque = false;
dialogController.View.BackgroundColor = UIColor.Clear.ColorWithAlpha(0.0f);
}
}
}
Et voila, now whenever we use the calls from the "Approach"-Section of this post, a nice popup dialog containing our custom Xamarin.Forms ContentPage will show up.
It is easier to use a package, like Plugins.Popup to achieve this, without custom renderers, it is impossible to add an Image to the default AlertDialog, which will limit you.
Using Popup Plugin, you just have to add it to your solution, initialize in both iOS and Android:
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init();
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new App ());
return base.FinishedLaunching (app, options);
}
}
Android:
namespace HelloXamarinFormsWorld.Android
{
[Activity(Label = "HelloXamarinFormsWorld", MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Rg.Plugins.Popup.Popup.Init(this, bundle);
Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication (new App ());
}
}
}
Create a Popup Page
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Center"
PositionOut="Center"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="400"
DurationOut="300"
EasingIn="SinOut"
EasingOut="SinIn"
HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<!--You can use any elements here which are extended from Xamarin.Forms.View-->
<StackLayout
VerticalOptions="Center"
HorizontalOptions="Center"
Padding="20, 20, 20, 20">
<Label
Text="Test"/>
</StackLayout>
And, to show in your page:
await Navigation.PushPopupAsync(page);
As of March 22, 2022, there is a Popup Included in the Xamarin Community Toolkit. You can find Microsoft's documentation here.
An object instantiation through XAML:
<xct:Popup xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="clr-namespace:Xamarin.CommunityToolkit.UI.Views;assembly=Xamarin.CommunityToolkit"
x:Class="MyProject.SimplePopup">
<StackLayout>
<Label Text="Hello Simple Popup" />
</StackLayout>
</xct:Popup>
An object instantiation through code:
using Xamarin.CommunityToolkit.UI.Views;
// can also specify a type to be returned from the dismissal of the popup
// e.g. var popup = new Popup<string>
var popup = new Popup
{
// Can set the content to any custom layout
Content = new StackLayout
{
Children =
{
new Label
{
Text = "Hello Simple Popup"
}
}
}
};
You can show the popup through the Navigation stack:
using Xamarin.CommunityToolkit.Extensions;
App.Current.MainPage.Navigation.ShowPopup(popup);
Or call it through await App.Current.MainPage.Navigation.ShowPopupAsync(popup);
It also has events you can intercept and handle, like popup.Dismissed.
The popup can even return a value when it is dismissed if you instantiate it with a type specified:
// provide the <T> type string here for the example
var popup = new Popup<string>
{
Content = new StackLayout
{
Children =
{
new Label
{
Text = "Hello Simple Popup"
},
new Button
{
Text = "Dismiss",
// pass the <T> you would like as the result into Dismiss
// in this case it is a string "Dismiss was clicked"
Command = new Command(() => Dismiss("Dismiss was clicked"))
}
}
}
};
var result = await App.Current.MainPage.Navigation.ShowPopupAsync(popup);
Console.WriteLine(result);
Use package named 'Rg.Plugin.Popup'. It will help you.

Observable Collection not updating the UI

I've read a dozen topics here, and I can't figure out what I'm doing wrong.
I have an ObservableCollection, I can add new Employees, but the UI is not displaying the changes right away. If I stop, and restart, the changes show up.
public class MainViewModel : ObservableObject
{
DataAccess.EmployeeRepository repository = new DataAccess.EmployeeRepository();
private ObservableCollection<Employee> employees;
public MainViewModel()
{
employees = new ObservableCollection<Employee>(repository.GetEmployees());
}
public ObservableCollection<Employee> Employees
{
get { return employees; }
set
{
employees = value;
OnPropertyChanged("Employees");
}
}
My XAML has the following binding:
<ListView Name="ListViewEmployeeDetails" Grid.Row="1" Margin="4,109,12,23" ItemsSource="{Binding Employees}" SelectedItem="{Binding CurrentEmployee}" >
<ListView.View>
<GridView x:Name="grdTest">
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding EmployeeID}" Width="100"/>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding EmployeeName}" Width="100" />
<GridViewColumn Header="Phone" DisplayMemberBinding="{Binding EmployeePhone}" Width="100" />
</GridView>
</ListView.View>
</ListView>
I can't figure out what is wrong. Any help is greatly appreciated!
public MainViewModel()
{
Employees = new ObservableCollection<Employee>(repository.GetEmployees());
}
You have to set Employees since you're firing the NotifyPropertyChanged event on its setter.

How to set textwrapping in Button

did a search on internet, could not find the solution. I think I missed something for below code to make the text wrap:
<Button x:Name="btnCustomerAging" Background="Green" BorderBrush="Green" Foreground="White" FontSize="33" HorizontalAlignment="Left" Margin="662,106,0,0" Grid.Row="1" VerticalAlignment="Top" Height="213" Width="238">
<TextWrapping>
Customer Locations
</TextWrapping>
</Button>
This will work.
<Button x:Name="btnCustomerAging" Background="Green" BorderBrush="Green" Foreground="White" FontSize="33"
HorizontalAlignment="Left" Margin="662,106,0,0" Grid.Row="1" VerticalAlignment="Top" Height="213" Width="238">
<TextBlock Text="Customer Locations" TextWrapping="Wrap" />
</Button>
You can create your own WrapButton and use it in XAML like this:
<local:WrapButton x:Name="MyButton" Text="Text that will wrap"/>
This is the code for WrapButton:
public sealed class WrapButton : Button
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(WrapButton), new PropertyMetadata(string.Empty));
public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }
public WrapButton()
{
var textBlock = new TextBlock { TextAlignment = TextAlignment.Center, TextWrapping = TextWrapping.Wrap };
textBlock.SetBinding(TextBlock.TextProperty, new Binding { Source = this, Path = new PropertyPath("Text") });
Content = textBlock;
}
}

What are the best practices to dynamically create views and add controls to view using MVVM pattern

Can anyone give out the best practices for playing around with controls at runtime such as creating a new view,adding views inside a view,adding controls to containers using MVVM pattern without breaking mvvm pattern??
I am using MVVMlight toolkit..
please help me out in this regard..
Thanks in advance......
This post discusses the strategies for creating views (dialogs) from your view models.
Edit:
From your comment I take it that you got an user interface that has an add and delete button. The add button should add an item (type ?) to a ItemsControl ... hope that's correct.
So, how would I do this, well I would create a view model that has an ObservableCollecion<ItemViewModel>. The ItemViewModle is the view mode that represents the item that should be added to the ItemsControl (so in your case the view model backing your "rangeView").
Then I would add two commands that handle the addition and deletion of items. Both commands just add/remove ItemViewModels from your collection.
To show the items in the view I would bind the ItemControl.ItemsSource property to the collection in your main view model (i.e. the one holding the ItemViewModel instances). The I would supply an ItemTemplate to render the items on the screen.
Ok, here is an example of what I think you are trying to do (at least conceptionally). Complete Source Code here. In the example I used a ListBox as it allows me easily to determine which item is selected, this depends on your szenario. Also note that you have complete freedom to customize the Template, the ItemPanelTemplate, and DataTemplate to fit your needs. You can even use this approacht to create PanoramaPages in WP7!
2nd edit: ItemsControl does not have a SelectedItem property. To get to it you have to use a control inheriting from Selector (e.g. a ListBox as I did) or you can use the Selector directly.
ItemViewModel:
public class ItemViewModel : ViewModelBase
{
#region [Name]
public const string NamePropertyName = "Name";
private string _name = null;
public string Name {
get {
return _name;
}
set {
if (_name == value) {
return;
}
var oldValue = _name;
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion
}
MainViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel() {
if (IsInDesignMode) {
this.Items = new ObservableCollection<ItemViewModel>(Enumerable.Range(0, 10).Select((x, i) => new ItemViewModel() { Name = "Design Time Item " + i }));
} else {
// Code runs "for real"
}
}
#region [AddCommand]
private RelayCommand _addCommand;
public RelayCommand AddCommand {
get {
return _addCommand ?? (_addCommand = new RelayCommand(
() => {
this.Items.Add(new ItemViewModel() { Name = "New item - " + DateTime.Now });
}
));
}
}
#endregion
#region [DeleteCommand]
private RelayCommand _deleteCommand;
public RelayCommand DeleteCommand {
get {
return _deleteCommand ?? (_deleteCommand = new RelayCommand(
() => {
this.Items.Remove(this.SelectedItem);
},
() => { return this.SelectedItem != null; }
));
}
}
#endregion
#region [Items]
public const string ItemsPropertyName = "Items";
private ObservableCollection<ItemViewModel> _items = new ObservableCollection<ItemViewModel>();
public ObservableCollection<ItemViewModel> Items {
get {
return _items;
}
set {
if (_items == value) {
return;
}
var oldValue = _items;
_items = value;
RaisePropertyChanged(ItemsPropertyName);
}
}
#endregion
#region [SelectedItem]
public const string SelectedItemPropertyName = "SelectedItem";
private ItemViewModel _selectedItem = null;
public ItemViewModel SelectedItem {
get {
return _selectedItem;
}
set {
if (_selectedItem == value) {
return;
}
var oldValue = _selectedItem;
_selectedItem = value;
RaisePropertyChanged(SelectedItemPropertyName);
// important in SL to notify command that can execute has changed !
this.DeleteCommand.RaiseCanExecuteChanged();
}
}
#endregion
}
MainPage.xaml
<UserControl
x:Class="MvvmLight1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="300"
Width="300"
DataContext="{Binding Main, Source={StaticResource Locator}}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- we are dealing with ItemViewModels now -->
<Border BorderThickness="0,0,0,1" BorderBrush="Gray" Padding="10,5">
<TextBlock Text="{Binding Name}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Margin="5,10" Content="Add" Command="{Binding AddCommand}" Width="70"/>
<Button Margin="5,10" Content="Delete" Command="{Binding DeleteCommand}" Width="70"/>
</StackPanel>
</Grid>
</UserControl>

Notification window in case of any operation in C: drive

I have created a sample for Notification window in silverlight4.
The issue i'm facing is that i want to show notification window whenever any operation(say file copied or deleted) is performed in my windows C: drive.
Since System.IO.FileSystemWatcher class (COM+ componenet) cannot be used in silverlight, so i found a way out using an AutomationFactory, it works like charm for me as long as i am using message box to show notification or default notification window but not in case of my own customnotification window.. :(
The code is like this -
new Thread(() =>
{
using (dynamic SWbemLocator = AutomationFactory.CreateObject("WbemScripting.SWbemLocator"))
{
SWbemLocator.Security_.ImpersonationLevel = 3;
SWbemLocator.Security_.AuthenticationLevel = 4;
dynamic IService = SWbemLocator.ConnectServer(".", #"root\cimv2");
string fileSystemWatcherQuery =
#"SELECT * FROM __InstanceOperationEvent WITHIN 3 WHERE Targetinstance ISA 'CIM_DirectoryContainsFile' and TargetInstance.GroupComponent= 'Win32_Directory.Name=""c:\\\\""'";
dynamic monitor = IService.ExecNotificationQuery(fileSystemWatcherQuery);
Dispatcher.BeginInvoke(() => MessageBox.Show(#"Now listening to file changes on c:\"));
while (true)
{
dynamic EventObject = monitor.NextEvent();
string eventType = EventObject.Path_.Class;
string path = EventObject.TargetInstance.PartComponent;
Dispatcher.BeginInvoke((Action)delegate
{
System.Windows.NotificationWindow notify = new System.Windows.NotificationWindow();
notify.Height = 74;
notify.Width = 329;
CustomNotification custom = new CustomNotification();
custom.Header = "FileSystemWatcher";
custom.Text = eventType + ":" + path;
custom.Width = notify.Width;
custom.Height = notify.Height;
custom.Closed += new EventHandler<EventArgs>(custom_Closed);
notify.Content = custom;
notify.Show(5000);
});
Dispatcher.BeginInvoke(() => MessageBox.Show(eventType + ": " + path));
}
}
}).Start();
Whenever i copied the file or delete a file from my C:, a message box appears and notification window do appears but the content is entirely blank. If i use default notification window in place of my own CustomNotification it works fine.
What i am guessing is, it has something related to threading, not sure though.. ??
CustomNotification is a class which derives from ContentConrol class and have two dependency properties namely "Header" & "Text". The control template for this i have declared in different xaml file like this -
<Style TargetType="local:CustomNotification">
<Setter.Value>
<ControlTemplate TargetType="local:CustomNotification">
<Border BorderBrush="Black" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="7"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFB9B9CC"/>
<GradientStop Color="#FF9191AB" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Grid Grid.Row="1">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFEDEDF5" Offset="0"/>
<GradientStop Color="#FFC4C3D7" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Button x:Name="closeButton" Grid.Column="2" VerticalAlignment="Top" Margin="1, 3" Width="16" Height="13">
<Button.Template>
<ControlTemplate TargetType="Button">
<Image Source="x.png"/>
</ControlTemplate>
</Button.Template>
</Button>
<StackPanel Grid.Column="1" Margin="5, 7">
<TextBlock Text="{TemplateBinding Header}" FontFamily="Verdana" FontWeight="Bold" FontSize="11"/>
<TextBlock Text="{TemplateBinding Text}" FontFamily="Verdana" FontSize="11" TextWrapping="Wrap"/>
</StackPanel>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here goes my CustomNotification class code -
public class CustomNotification : ContentControl
{
public CustomNotification()
{
this.DefaultStyleKey = typeof(CustomNotification);
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(string),
typeof(CustomNotification),
new PropertyMetadata(OnHeaderPropertyChanged));
/// <summary>
/// Gets or sets a value that indicates whether
/// the <see cref="P:System.Windows.Controls.Label.Target" /> field is required.
/// </summary>
public string Header
{
get
{
return (string)GetValue(CustomNotification.HeaderProperty);
}
set
{
SetValue(CustomNotification.HeaderProperty, value);
}
}
private static void OnHeaderPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(CustomNotification),
new PropertyMetadata(OnTextPropertyChanged));
/// <summary>
/// Gets or sets a value that indicates whether
/// the <see cref="P:System.Windows.Controls.Label.Target" /> field is required.
/// </summary>
public string Text
{
get
{
return (string)GetValue(CustomNotification.TextProperty);
}
set
{
SetValue(CustomNotification.TextProperty, value);
}
}
private static void OnTextPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button closeButton = GetTemplateChild("closeButton") as Button;
if (closeButton != null)
{
closeButton.Click += new RoutedEventHandler(closeButton_Click);
}
}
public event EventHandler<EventArgs> Closed;
void closeButton_Click(object sender, RoutedEventArgs e)
{
EventHandler<EventArgs> handler = this.Closed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
I assume your CustomNotification class is just a user control (i.e. inherits directly from UserControl)? Assuming so, this line:
notify.Content = tb;
is wrong. I don't know what tb is, but it should be:
notify.Content = custom;
Then you'll see the content.
Note that you have to be careful to not try and show a notification whilst another one is currently being displayed. You'll get an exception, so you'll need to keep track of this.
Hope this helps...
Chris Anderson

Resources