assign click event to a Button in a DataTemplate - c#

<Window x:Class="WpfApp12.MainWindow"
xmlns=...usual namespaces...
Loaded="Window_Loaded"
>
<Window.Resources>
<DataTemplate x:Key="myHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{TemplateBinding Content}"/>
<Button Margin="5,0,0,0" x:Name="MyButton">Press me</Button>
</StackPanel>
</DataTemplate>
</Window.Resources>
<!-- Just point the datacontext to the code behind -->
<Window.DataContext>
<Binding RelativeSource="{RelativeSource Mode=Self}"/>
</Window.DataContext>
<DataGrid Name="DG" ItemsSource="{Binding People}"/>
</Window>
This, togheter with the code behind below gives just what I want: a DataGrid with a column whose header has been dynamically assigned a DataTemplate with a Button "Press me":
The code behind:
public partial class MainWindow : Window
{
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
public MainWindow()
{
People.Add(new Person() { Name = "Isaac", Surname = "Newton" });
People.Add(new Person() { Name = "Galileo", Surname = "Galilei" });
InitializeComponent();
}
public ObservableCollection<Person> People { get; } = new ObservableCollection<Person>();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DG.Columns[0].HeaderTemplate = (DataTemplate)FindResource("myHeaderTemplate");
//how to access the button in the template in order to assign the click event?
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
//DO SOMETHING
}
}
}
Now i want to dynamically wire MyButton_Click event to the button in the template.
This kind of problems seem to have a lot of coverage, this one being one of the best:
WPF How to access control from DataTemplate
There there is something like:
ComboBox myCombo = _contentPresenter.ContentTemplate.FindName("myCombo", _contentPresenter) as ComboBox;
I'm not very familiar with the templating, and I cannot find the starting point, the "content presenter" on which to call the FindName.

You could use a recursive helper method that finds the Button in the visual tree:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DG.Columns[0].HeaderTemplate = (DataTemplate)FindResource("myHeaderTemplate");
DG.Dispatcher.BeginInvoke(new Action(() =>
{
DataGridColumnHeadersPresenter presenter = FindVisualChild<DataGridColumnHeadersPresenter>(DG);
DataGridCellsPanel dataGridCellsPanel = FindVisualChild<DataGridCellsPanel>(presenter);
DataGridColumnHeader header = dataGridCellsPanel.Children[0] as DataGridColumnHeader;
Button button = FindVisualChild<Button>(header);
if (button != null)
button.Click += MyButton_Click;
}));
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
//DO SOMETHING
}
private static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
return (T)child;
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}

Related

Can I get access from code behind (of a ResourceDictionary) to a named control?

Is it possible to get access from a code behind (of a ResourceDictionary) to a named control?
E.g. for me it is necessary to create lots of folder picking dialogs. A dialog may contain several rows for each folder that has to be chosen.
Each row consists of: Label (Name), TextBox (Chosen Path) and a Button (opens FileBrowserDialog).
So now I want to access the TextBox when the FileBrowserDialog is finished. But I can not access the "SelectedFolderTextBox" from CodeBehind.
Is there a better way to achieve what I want to do?
XAML
<ResourceDictionary ...>
...
<StackPanel x:Key="FolderSearchPanel"
x:Shared="False">
<Label Content="Foldername"/>
<TextBox x:Name="SelectedFolderTextBox"
Text="C:\Folder\Path\"/>
<Button Content="..."
Click="Button_Click"/>
</StackPanel>
</ResourceDictionary>
CodeBehind
private void Button_Click(object sender, RoutedEventArgs e)
{
// Initialize and show
var dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
// Process result
if (result == System.Windows.Forms.DialogResult.OK)
{
string selectedPath = dialog.SelectedPath;
SelectedFolderTextBox.Text = selectedPath; // THIS DOES NOT WORK
// since I don't have access to it
// but describes best, what I want to do
}
}
You should be able to cast the sender argument to a Button and then cast the Parent property of the Button to a StackPanel and find the controls in the Children collection of the StackPanel. Something like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
// Initialize and show
var dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
// Process result
if (result == System.Windows.Forms.DialogResult.OK)
{
string selectedPath = dialog.SelectedPath;
Button clickedButton = sender as Button;
StackPanel sp = clickedButton.Parent as StackPanel;
if (sp != null)
{
TextBox SelectedFolderTextBox = sp.Children.OfType<TextBox>().FirstOrDefault(x => x.Name == "SelectedFolderTextBox");
if (SelectedFolderTextBox != null)
SelectedFolderTextBox.Text = selectedPath;
}
}
}
when you have a repeated group of controls and a bit of associated functionality it makes sense to create a reusable control:
Add UserControl via project "Add Item" dialog and use this xaml and code:
<UserControl x:Class="WpfDemos.FolderPicker"
x:Name="folderPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="75" d:DesignWidth="300">
<StackPanel>
<Label Content="{Binding Path=Title, ElementName=folderPicker}"/>
<TextBox x:Name="SelectedFolderTextBox"
Text="{Binding Path=FullPath, ElementName=folderPicker,
UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="..." Click="PickClick"/>
</StackPanel>
</UserControl>
public partial class FolderPicker : UserControl
{
public FolderPicker()
{
InitializeComponent();
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
"Title", typeof (string), typeof (FolderPicker), new PropertyMetadata("Folder"));
public string Title
{
get { return (string) GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty FullPathProperty = DependencyProperty.Register(
"FullPath", typeof (string), typeof (FolderPicker), new FrameworkPropertyMetadata(#"C:\", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string FullPath
{
get { return (string) GetValue(FullPathProperty); }
set { SetValue(FullPathProperty, value); }
}
private void PickClick(object sender, RoutedEventArgs e)
{
using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
FullPath = dialog.SelectedPath;
}
}
}
TextBox is accessible from code-behind. Dependency properties Title and FullPath allows to customize control for different usages and create bindings with a view model (something you can't do with controls group declared as resource). Example
view model:
public class MyViewModel
{
public string Src { get; set; }
public string Target { get; set; }
}
view:
public MyWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel { Src = "C:", Target = "D:" }
}
<StackPanel>
<wpfDemos:FolderPicker Title="Source" FullPath="{Binding Path=Src}" />
<wpfDemos:FolderPicker Title="Destination" FullPath="{Binding Path=Target}"/>
</StackPanel>

Bindable property not working in user control wpf

I have User Control and create DepedencyProperty "ItemSource" inside that user control like this
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
"ItemsSource", typeof(IEnumerable), typeof(DataGridFilterDetail),
new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
NotifyPropertyChanged("ItemsSource");
}
}
static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as DataGridFilterDetail;
if (control != null)
{
control.OnItemsSourceChanged((IEnumerable)e.OldValue(IEnumerable)e.NewValue);
}
}
void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
// Remove handler for oldValue.CollectionChanged
var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged;
if (null != oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
// Add handler for newValue.CollectionChanged (if possible)
var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (null != newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
}
void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems == null)
{
TotalCount = 0;
}
else
{
TotalCount = e.NewItems.Count;
}
}
And then i bind "ItemSource" into ItemSource of my DataGrid inside UserControl like this.
<UserControl x:Class="WPF.Controls.DataGridFilterDetail" x:Name="dataGridFilterDetail" .../>
<DataGrid ItemsSource="{Binding ElementName=dataGridFilterDetail,Path=ItemsSource}" AutoGenerateColumns="True"/>
</UserControl>
and then i using this user control inside my view.
<UserControl x:Class="Project.View.MyView" ..
<UserControl.Resources>
<vm:MyViewModel x:Key="myViewModel" />
</UserControl.Resources>
<Grid>
<custom:DataGridFilterDetail ItemsSource="{Binding GridItemCollections, Source={StaticResource myViewModel}}" />
</Grid>
</UserControl>
inside MyView.cs Constructor i set my view datacontext :
public MyView()
{
InitializeComponent();
myViewModel= (MyViewModel)this.Resources["myViewModel"];
this.DataContext = myViewModel;
}
on myViewModel i create property "GridItemCollections" like this
private ObservableCollection<myCustomClass> gridItemCollections;
public ObservableCollection<myCustomClass> GridItemCollections
{
get { return gridItemCollections; }
set
{
gridItemCollections = value;
NotifyPropertyChanged("GridItemCollections");
}
}
public void AddNewGridRow()
{
GridItemCollections.Add(CurrentDetail);
}
now the problem is, why grid inside user control not updating when i modify "GridItemCollections" on my view model?is there something that i missing here?
Thanks in advance.

Change main window implementation to user control

I need to replace the following code usercontrol instead of main window (of course the main window is just calling to the userControl),while doing that I have the following problem.
Currently I tried to add the following code like in the main window in the
constructor of the usercontrol after the
public partial class UserControl: UserControl
{
private static MappingViewModelView _modelViewInstance;
public UserControl()
{
InitializeComponent();
_modelViewInstance = new MappingViewModelView();
DataContext = _modelViewInstance;
var source = Resources["source"] as CollectionViewSource;
if (source != null)
source.Source = _modelViewInstance.UserList;
ListBox.SelectionChanged += listbox_SelectionChanged;
But now in the user control there is no event SelectionChanged for list box (using the intlisense) just ListBox.SelectionChangedEvent which is not fit the original solution from the main window which is
ListBox.SelectionChanged += listbox_SelectionChanged;
error which is given if i put the exact code is: Cannot access non-static event 'SelectionChanged' in static context
Any idea why the list box behave different in the user control?
public partial class MainWindow : Window
{
public ObservableCollection<User> _UsersList = new ObservableCollection<User>();
private readonly Dictionary<string, string> _mapping = new Dictionary<string, string>();
private const string DRAG_SOURCE = "DragSource";
public MainWindow()
{
InitializeComponent();
_UsersList.Add(new User { Name = "Jhon" });
_UsersList.Add(new User { Name = "Mike" });
_UsersList.Add(new User { Name = "Alex" });
_UsersList.Add(new User { Name = "Darl" });
CollectionViewSource source = this.Resources["source"] as CollectionViewSource;
source.Source = _UsersList;
ListBox.SelectionChanged += listbox_SelectionChanged;
DataObject.AddCopyingHandler(text1, DragCopy);
DataObject.AddCopyingHandler(text2, DragCopy);
}
public ObservableCollection<User> UserList
{
get { return _UsersList; }
}
private void listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 1)
{
if (ListBox.SelectedItems.Count > 0)
{
var mySelectedItem = ListBox.SelectedItem as User;
if (mySelectedItem != null)
{
DragDrop.DoDragDrop(ListBox, mySelectedItem,
DragDropEffects.Copy | DragDropEffects.Move);
}
}
}
}
private void DropText_PreviewDragEnter(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.None;
}
private void DropText_PreviewDrop(object sender, DragEventArgs e)
{
var textbox = (TextBox)sender;
if (!(textbox.Text.Length > 0))
{
DataObject data = e.Data as DataObject;
User user = data.GetData(typeof(User)) as User;
textbox.Tag = user;
var name = user.Name;
textbox.Text += name;
textbox.Focus();
textbox.CaretIndex = textbox.Text.Length;
e.Handled = true;
var remove = _UsersList.Remove((User)ListBox.SelectedItem);
if (!_mapping.ContainsKey(textbox.Name))
_mapping.Add(textbox.Name, name);
}
e.Handled = true;
}
private void DropText_PreviewDragOver(object sender, DragEventArgs e)
{
e.Handled = true;
}
private void ListBox_Drop(object sender, DragEventArgs e)
{
DataObject data = e.Data as DataObject;
if (data != null)
{
User user = data.GetData(typeof(User)) as User;
if (user != null && !_UsersList.Contains(user))
_UsersList.Add(user);
}
TextBox txtBox = e.Data.GetData(DRAG_SOURCE) as TextBox;
if (txtBox != null)
{
if (_mapping.ContainsKey(txtBox.Name))
_mapping.Remove(txtBox.Name);
txtBox.Dispatcher.BeginInvoke((Action)(() => { txtBox.Text = string.Empty; }), System.Windows.Threading.DispatcherPriority.Normal);
}
e.Handled = true;
}
private void DragCopy(object sender, DataObjectCopyingEventArgs e)
{
if (e.IsDragDrop)
{
e.CancelCommand();
TextBox txtBox = sender as TextBox;
if (txtBox != null && txtBox.Tag != null)
{
DataObject dataObject = new DataObject(txtBox.Tag);
dataObject.SetData(DRAG_SOURCE, txtBox);
DragDrop.DoDragDrop(sender as DependencyObject, dataObject, DragDropEffects.Move | DragDropEffects.Copy);
}
e.Handled = true;
}
}
private void DropText_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox txtBox = sender as TextBox;
if (txtBox.Text == string.Empty)
{
User user = txtBox.Tag as User;
if(user != null && !_UsersList.Contains(user))
_UsersList.Add(user);
if (_mapping.ContainsKey(txtBox.Name))
_mapping.Remove(txtBox.Name);
}
}
}
ListBox x:Name="ListBox" HorizontalAlignment="Left" Height="115"
VerticalAlignment="Top" Width="150" ItemsSource="{Binding Source={StaticResource source}}"
DisplayMemberPath="Name"
AllowDrop="True" Drop="ListBox_Drop" />
<Window.Resources>
<CollectionViewSource x:Key="source">
</CollectionViewSource>
</Window.Resources>
<TextBox x:Name="text1"
AcceptsReturn="True"
AllowDrop="True"
PreviewDragEnter="DropText_PreviewDragEnter"
PreviewDrop="DropText_PreviewDrop"
PreviewDragOver="DropText_PreviewDragOver"
TextChanged="DropText_TextChanged"
Grid.Column="1"
HorizontalAlignment="Left" Height="23" TextWrapping="Wrap"
VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="text2"
AcceptsReturn="True"
AllowDrop="True"
PreviewDragEnter="DropText_PreviewDragEnter"
PreviewDrop="DropText_PreviewDrop"
PreviewDragOver="DropText_PreviewDragOver"
TextChanged="DropText_TextChanged"
Grid.Column="1"
HorizontalAlignment="Left" Height="23" TextWrapping="Wrap"
VerticalAlignment="Top" Width="120"/>
If you moved the ListBox to the UserControl, the most likely reasen is that - maybe by accident - the name was changed in the process.
The error message points in this direction as it recognizes ListBox as the type and not as the name of a concrete instance of a ListBox on the UserControl.

How do I retrieve text from a textblock inside a listbox and display the text in a textbox?

How do I retrieve text from a textblock inside a listbox and display the text in a textbox?
What I want to do
First I want to be able to copy the text from the textblock inside the listbox
Then I want to display the text in the textbox
I tried using a visual tree helper but apparently it cannot find the 'FindName' method. Is there a better way to achieve this?
XAML Code
<ListBox Name="ChatDialogBox" Height="550" ItemsSource="{Binding Path=Instance.Messages,Source={StaticResource Binder}}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Name="ChatMessage" Text="{Binding Text}" TextWrapping="Wrap" Width="430">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu Name="ContextMenu" >
<toolkit:MenuItem Name="Copy" Header="Copy" Click="Copy_Click" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code Behind
private void Copy_Click(object sender, RoutedEventArgs e)
{
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(ChatDialogBox);
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
TextBlock target = (TextBlock)myDataTemplate.FindName("ChatMessage", myContentPresenter);
}
private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Binder Class
public class Binder : INotifyPropertyChanged
{
static Binder instance = null;
static readonly object padlock = new object();
public Binder()
{
Messages = new ObservableCollection<Message>();
}
public static Binder Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new Binder();
}
return instance;
}
}
}
private ObservableCollection<Message> messages;
public ObservableCollection<Message> Messages
{
get
{
return messages;
}
set
{
if (messages != value)
{
messages = value;
NotifyPropertyChanged("Messages");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
Deployment.Current.Dispatcher.BeginInvoke(() => { PropertyChanged(this, new PropertyChangedEventArgs(info)); });
}
}
}
Message Class
public class Message
{
public string Text { get; set; }
}
There's an indirect but easier way to retrieve the content of the textblock.
In the click event, you can retrieve the object your model by using the DataContext property:
private void Copy_Click(object sender, RoutedEventArgs e)
{
var model = (Message)((FrameworkElement)sender).DataContext;
// Display model.Text in your TextBlock
}
Just replace Message by the type of the objects you've assigned to the ItemsSource of your listbox.

How to get item details from an ItemCollection in a GridView

I've a GridView page with different elements (ItemExplorer). Each element has two TextBlocks (Name & Description) and they're binding to a Collection.
I want that to when I click in a single element, a new page should be opened with the item details (ItemViewer), like a name or description...
I'm new in C#, so I appreciate any help or support! :)
This is my current code:
ItemExplorer.xaml:
<GridView ItemsSource="{Binding ItemList}">
<GridView.ItemTemplate>
<DataTemplate>
<Border DoubleTapped="GoToItemViewer_DoubleTapped">
<StackPanel>
<TextBlock Name="ItemName" Text="{Binding ItemName}"/>
<TextBlock Name="ItemDescription" Text="{Binding ItemDescription}"/>
</StackPanel>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
ItemExplorer.xaml.cs:
namespace Test001.Pages
{
public sealed partial class ItemExplorer : Page
{
public ItemCollection MyItemCollection;
public ItemExplorer()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
MyItemCollection = new ItemCollection();
this.DataContext = MyItemCollection;
}
// Go to ItemViewer
private void GoToItemViewer_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
if (this.Frame != null)
{
// SEND THE ITEM DETAILS AS PARAMTER TO ITEMVIEWER
this.Frame.Navigate(typeof(ItemViewer));
}
}
}
}
ItemCollection.cs
Collectionnamespace Test001.DataSource
{
public class CollectionFiles: BindableBase
{
private ItemCollection _ItemList = new ItemCollection();
public ItemCollection ItemList
{
get { return _ItemList; }
set { SetProperty(ref _ItemList, value); }
}
public CollectionFiles()
{
_ItemList.Add(new FileModel()
{
ItemName = "ItenName0",
ItemDescription = "Iten Description0",
});
_ItemList.Add(new FileModel()
{
ItemName = "ItenName1",
ItemDescription = "Iten Description1",
});
_ItemList.Add(new FileModel()
{
ItemName = "ItenName2",
ItemDescription = "Iten Description2",
});
_ItemList.Add(new FileModel()
{
ItemName = "ItenName3",
ItemDescription = "Iten Description3",
});
}
}
}
ItemViewer.xaml.cs
namespace mydox104.Pages
{
public sealed partial class DocumentViewer : mydox104.Common.LayoutAwarePage
{
public DocumentViewer()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// GET THE ITEM DETAILS
string file_name = e.Parameter as string;
if (!string.IsNullOrWhiteSpace(file_name))
{
pageTitle.Text = file_name;
}
else
{
pageTitle.Text = e.Parameter.ToString();
}
}
}
}
ItemViewer.xaml
<Grid Height="225">
<TextBlock x:Name="Item-Name" Text=""/>
<TextBlock x:Name="Item-Description" Text=""/>
</Grid>
Set in GridView:
<GridView IsItemClickEnabled="True" ItemClick="ItemClicked" ItemsSource="{Binding ItemList}">
in ItemExplorer.xaml.cs
private void ItemClicked(object sender, ItemClickEventArgs e)
{
var clickedItem = e.ClickedItem as ItemCollection;
if (clickedItem != null )
{
this.Frame.NavigateTo(typeof(ItemViewer), clickedItem);
}
}
in ItemViewer.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ItemCollection myElement = e.Parameter as ItemCollection;
...
}
Instead of
public ItemCollection MyItemCollection;
i'd just use
public ObservableCollection<FileModel> Items;
I hope it will help you somehow. However I encourage you to look about MVVM pattern. It is really useful in developing WPF/WinRT applications. (check also MVVM Light framework which gives you for instance Messanger class - which have ability to "send objects/messages" between classes).

Categories

Resources