I was watching James Montemagno's YouTube video on weak reference managers and I wondered if it would work in .Net Framework. So I created this WPF, .Net Framework version of his project.
Start a new WPF .Net Framework Visual Studio project using C# and call it Messaging. Add another WPF Window called DetailWindow. Use the GitHub solution manager to add the CommunityToolkit.Mvvm package by Microsoft. I'm using version 8.1.
The finished application will let you create a task list, double-click on a task to see a detail window, and delete the task from the detail window. The detail window does not delete the task - it sends a message to the main window to do it. That's the interesting bit.
Let's start with the main window's XAML and code behind, without the messaging goodness.
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"
xmlns:local="clr-namespace:Messaging"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<RoutedCommand x:Key="AddCommand"/>
<RoutedCommand x:Key="DetailCommand"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource AddCommand}" CanExecute="Add_CanExecute" Executed="Add_Executed"/>
<CommandBinding Command="{StaticResource DetailCommand}" CanExecute="Detail_CanExecute" Executed="Detail_Executed"/>
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding TextToAdd, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
<Button Grid.Row="0" Grid.Column="1" Content="Add" Command="{StaticResource AddCommand}" Margin="10,5,5,5" Width="100" Height="22"/>
BorderThickness="0,2,0,0" IsReadOnly="True"
ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{StaticResource DetailCommand}"/>
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Text}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
-----------------------------------------
namespace Messaging
public class cTask
{
public String Text { get; set; }
public partial class MainWindow : Window, INotifyPropertyChanged, IRecipient<DeleteItemMessage>
private ObservableCollection<cTask> tasks = new ObservableCollection<cTask>();
get { return tasks; }
private cTask selectedTask = null;
get { return selectedTask; }
private String textToAdd = "";
get { return textToAdd; }
public MainWindow()
InitializeComponent();
}
private void Add_CanExecute(object sender, CanExecuteRoutedEventArgs e)
e.CanExecute = (!string.IsNullOrEmpty(textToAdd));
private void Add_Executed(object sender, ExecutedRoutedEventArgs e)
if (!string.IsNullOrEmpty(TextToAdd))
tasks.Add(new cTask() { Text = textToAdd });
}
private void Detail_CanExecute(object sender, CanExecuteRoutedEventArgs e)
e.CanExecute = (selectedTask != null);
private void Detail_Executed(object sender, ExecutedRoutedEventArgs e)
DetailWindow detailWindow = new DetailWindow(SelectedTask);
}
public event PropertyChangedEventHandler PropertyChanged;
if (!Object.Equals(storage, value))
storage = value;
if (PropertyChanged != null)
}
}
}
Now let's do the same for the detail window.
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"
xmlns:local="clr-namespace:Messaging"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Detail Window" Height="450" Width="800">
<Window.Resources>
<RoutedCommand x:Key="DeleteCommand"/>
<RoutedCommand x:Key="CloseCommand"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource DeleteCommand}" Executed="Delete_Executed"/>
<CommandBinding Command="{StaticResource CloseCommand}" Executed="Close_Executed"/>
</Window.CommandBindings>
<StackPanel Orientation="Vertical">
<Border BorderThickness="2" BorderBrush="DarkBlue" CornerRadius="5" Padding="5">
<TextBlock Text="{Binding Task.Text}"/>
</Border>
<Button Content="Delete" Background="Red" Command="{StaticResource DeleteCommand}"/>
<Button Content="Close" Command="{StaticResource CloseCommand}"/>
</StackPanel>
</Window>
----------------------------------------------------
namespace Messaging
public partial class DetailWindow : Window, INotifyPropertyChanged
{
private cTask task;
get { return task; }
public DetailWindow(cTask task)
InitializeComponent();
this.Task= task;
private void Delete_Executed(object sender, ExecutedRoutedEventArgs e)
}
private void Close_Executed(object sender, ExecutedRoutedEventArgs e)
DialogResult = false;
public event PropertyChangedEventHandler PropertyChanged;
if (!Object.Equals(storage, value))
storage = value;
if (PropertyChanged != null)
}
}
}
If you run this now you can add tasks to the list and double-click a task to see the detail. The only functionality not implemented is the delete button.
To send the message, we need to define the message type. In MainWindow add a new class after cTask.
public DeleteItemMessage(cTask value) : base(value)
}
}
Now we have defined our message we can send and receive it. To send it, add the send command to the Delete_Executed event handler in DetailWindow.
WeakReferenceMessenger.Default.Send(new DeleteItemMessage(task));
Add an interface to the MainWindow class declaration
public partial class MainWindow : Window, INotifyPropertyChanged, IRecipient<DeleteItemMessage>
Register to receive specific messages in the constructor (or wherever)
InitializeComponent();
WeakReferenceMessenger.Default.Register<DeleteItemMessage>(this);
and write the Receive method required by the interface.
Tasks.Remove(message.Value);
}