How to make my control focusable? - wpf

I have a control
public class FocusTestControl : Control {
public FocusTestControl() {
DefaultStyleKey = typeof(FocusTestControl);
}
}
Here is it's default style
<Style
TargetType="local:FocusTestControl">
<Setter
Property="Focusable"
Value="True" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border
Background="AliceBlue" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I put this control on a window:
<Window
x:Class="MakeWpfControlFocusable.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MakeWpfControlFocusable"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition
Height="35" />
</Grid.RowDefinitions>
<local:FocusTestControl />
<StackPanel
Grid.Row="1"
Orientation="Horizontal">
<TextBlock
Text="Focused element: " />
<TextBlock
Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=KeyboardFocusedElement}" />
<TextBox
Text="Text" />
</StackPanel>
</Grid>
But clicking on control doesn't make it focused (I mean KebordFocus)
Actually, my task is handle the KeyDown and KeyUp events. But it is impossible when element has no keybord focus.

Keybord focus can be set by pessing Tab keyboard key. But is is not set when click on the control. I've subscribed to the MouseDown event and set focus manually in the handler.
public class FocusTestControl : Control, IInputElement {
public FocusTestControl() {
DefaultStyleKey = typeof(FocusTestControl);
MouseDown += FocusTestControl_MouseDown;
}
void FocusTestControl_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
Keyboard.Focus(this);
}
}

Related

WPF Data Binding From UserControl

I want to bind 'SomeText' from my UserControl, into the Content of my Label.
I currently have a UserControl which just displays my 'SomeText'. The XAML, and Code Behind file can be seen below.
<UserControl x:Class="TabHeader.UserControl1"
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="183" d:DesignWidth="235"
x:Name="uc">
<Grid>
<Label Height="43" HorizontalAlignment="Left" Margin="57,102,0,0" Name="textBlock1" Content="{Binding Path=SomeText, ElementName=uc}" VerticalAlignment="Top" Width="86" />
</Grid>
</UserControl>
namespace TabHeader
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
private string someText;
public UserControl1()
{
this.SomeText = "23";
InitializeComponent();
}
public string SomeText
{
get
{
return someText;
}
set
{
someText = value;
}
}
}
}
I then have my main XAML page where I have, a Tab Control within a Grid. I'm using a Style to generate two Labels within the Columns Header. I am able to pull through the Header field, but I am unable to pull through the controls field.
<Window x:Class="TabHeader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:TabHeader"
Title="MainWindow" Height="350" Width="525" Name="Tabs">
<Grid>
<TabControl Height="262" HorizontalAlignment="Left" Margin="47,26,0,0" Name="tabControl1" VerticalAlignment="Top" Width="366">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="tabItemHeaderStyle" >
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type TabItem}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
<Label Content="{Binding Path=SomeText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=vw:UserControl1}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Style="{StaticResource tabItemHeaderStyle}" Header="TI 1" Name="tabItem1" Width="100">
<vw:UserControl1 x:Name="UserControl11"></vw:UserControl1>
</TabItem>
<TabItem Style="{StaticResource tabItemHeaderStyle}" Header="TI 2" Name="tabItem2">
</TabItem>
</TabControl>
</Grid>
</Window>
Any assistance with this would be greatly appreciated.
Cheers.
Edit 1
For anyone interested added my working code below, where I have used the DependencyProperty.
MainWindow.xaml
<Grid>
<TabControl Height="262" HorizontalAlignment="Left" Margin="47,26,0,0" Name="tabControl1" VerticalAlignment="Top" Width="366">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="tab1ItemHeaderStyle">
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type TabItem}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
<Label Content="{Binding Path=UC1Figure, ElementName=uc1}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Style="{StaticResource tab1ItemHeaderStyle}" Header="[Tab 1]" Name="tabItem1" Width="100">
<vw:UserControl1 x:Name="uc1"></vw:UserControl1>
</TabItem>
<TabControl>
</Grid>
UserControl1.xaml
<Grid>
<Label Height="43" HorizontalAlignment="Left" Margin="69,128,0,0" Name="textBlock1" Content="{Binding Path=UC1Figure, ElementName=uc}" VerticalAlignment="Top" Width="100" />
<Button Name="updateSomeFigure" Content="Press Me" Click="updateSomeFigure_Click" Width="100" Height="100" Margin="69,12,66,71" />
</Grid>
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static readonly DependencyProperty SomeFigureProperty =
DependencyProperty.Register("UC1Figure", typeof(int), typeof(UserControl1));
public int UC1Figure
{
get { return (int)this.GetValue(SomeFigureProperty); }
set { this.SetValue(SomeFigureProperty, value); }
}
private void updateSomeFigure_Click(object sender, RoutedEventArgs e)
{
UC1Figure = UC1Figure + 1;
}
}
If you want to data bind a property to the UI of your UserControl, you have two options. The first is to implement the INotifyPropertyChanged Interface in your code behind. The second is to define DependencyPropertys instead of regular CLR properties. You can find out how to do that in the Dependency Properties Overview page on MSDN.
You might also want to read the Data Binding Overviewā€ˇ page on MSDN before you start data Binding.

XAML to add header to radio button

So with a lot of looking around I am hoping to make a GroupBox that acts like a Radio button. The header section would act as the bullet. I took some code from this question
Styling a GroupBox
that is how I want it to look. But I want to have it as a Radio button. So I put in this code (mind you I've only been doing WPF for a week or 2 now)
<Style TargetType="{x:Type RadioButton}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<BulletDecorator>
<BulletDecorator.Bullet>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="SelectedBorder"
Grid.Row="0"
Margin="4"
BorderBrush="Black"
BorderThickness="1"
Background="#25A0DA">
<Label x:Name="SelectedLabel" Foreground="Wheat">
<ContentPresenter Margin="4" />
</Label>
</Border>
<Border>
</Border>
</Grid>
</BulletDecorator.Bullet>
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="SelectedBorder" Property="Background" Value="PaleGreen"/>
<Setter TargetName="SelectedLabel"
Property="Foreground"
Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have a feeling that I can add a label to the second row of my grid, but then I don't know how to access it. I have that template in a test project in the Window.Resources section (I plan on moving it to a resource dictionary in my main project)
the xaml for my window is this
<Grid>
<GroupBox Name="grpDoor" Margin ="8" Grid.Row="0" Grid.Column="0">
<GroupBox.Header>
WPF RadioButton Template
</GroupBox.Header>
<StackPanel Margin ="8">
<RadioButton FontSize="15" Content="Dhaka" Margin="4" IsChecked="False"/>
<RadioButton FontSize="15" Content="Munshiganj" Margin="4" IsChecked="True" />
<RadioButton FontSize="15" Content="Gazipur" Margin="4" IsChecked="False" />
</StackPanel>
</GroupBox>
</Grid>
I then hoping for something like this (not sure how I'd do it yet though)
<Grid>
<GroupBox Name="grpDoor" Margin ="8" Grid.Row="0" Grid.Column="0">
<GroupBox.Header>
WPF RadioButton Template
</GroupBox.Header>
<StackPanel Margin ="8">
<RadioButton FontSize="15"
Content="Dhaka"
Margin="4"
IsChecked="False">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
<RadioButton FontSize="15"
Content="Munshiganj"
Margin="4"
IsChecked="True">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
<RadioButton FontSize="15"
Content="Gazipur"
Margin="4"
IsChecked="False">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
</StackPanel>
</GroupBox>
</Grid>
Based on your clarification, here is a very simple example with a RadioButton that looks like a GroupBox.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:SimpleViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:SimpleOption}">
<RadioButton GroupName="choice" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<RadioButton.Template>
<ControlTemplate TargetType="{x:Type RadioButton}">
<GroupBox x:Name="OptionBox" Header="{Binding Path=DisplayName, Mode=OneWay}">
<TextBlock Text="{Binding Path=Description, Mode=OneWay}"/>
</GroupBox>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, Mode=OneWay}" Value="True">
<Setter TargetName="OptionBox" Property="Background" Value="Blue"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=Options, Mode=OneWay}"/>
</Grid>
</Window>
public class SimpleViewModel
{
public SimpleViewModel()
{
Options = new ObservableCollection<SimpleOption>();
var _with1 = Options;
_with1.Add(new SimpleOption {
DisplayName = "Dhaka",
Description = "This is a description for Dhaka."
});
_with1.Add(new SimpleOption {
DisplayName = "Munshiganj",
Description = "This is a description for Munshiganj.",
IsSelected = true
});
_with1.Add(new SimpleOption {
DisplayName = "Gazipur",
Description = "This is a description for Gazipur."
});
}
public ObservableCollection<SimpleOption> Options { get; set; }
}
public class SimpleOption : INotifyPropertyChanged
{
public string DisplayName {
get { return _displayName; }
set {
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
private string _displayName;
public string Description {
get { return _description; }
set {
_description = value;
NotifyPropertyChanged("Description");
}
}
private string _description;
public bool IsSelected {
get { return _isSelected; }
set {
_isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
private bool _isSelected;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
}
I'd do it with a custom attached property. That way, you can bind to it from a ViewModel, or apply it directly in XAML.
First, create a new class in your Style assembly:
public static class RadioButtonExtender
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.RegisterAttached(
"Description",
typeof(string),
typeof(RadioButtonExtender),
new FrameworkPropertyMetadata(null));
[AttachedPropertyBrowsableForType(typeof(RadioButton))]
public static string GetDescription(RadioButton obj)
{
return (string)obj.GetValue(DescriptionProperty);
}
public static void SetDescription(RadioButton obj, string value)
{
obj.SetValue(DescriptionProperty, value);
}
}
And your style's Bullet would change so that the label is:
<Label x:Name="SelectedLabel"
Foreground="Wheat"
Content="{Binding (prop:RadioButtonExtender.Description), RelativeSource={RelativeSource TemplatedParent}} />
You could then use it in your final XAML:
<RadioButton FontSize="15"
Content="Dhaka"
Margin="4"
IsChecked="False">
<prop:RadioButtonExtender.Description>
This is a description that would show under my Header
</prop:RadioButtonExtender.Description>
</RadioButton>
As an added bonus, since you're creating the Style in a separate assembly, you can create a custom XAML namespace to make using your property easier.

Change input form into a display form

Some context:
Users enter data into a Window with multiple input control (standard textbox, combobox etc).
Users open same window in -readmode- displaying previously entered data.
Sure, input form is easy and for readmode I can use the IsEnabled dependancyproperty to disable the input controls.
Is it possible to replace all input controls with labels using Style with Triggers?
This will turn all TextBoxes into TextBlocks when IsReadOnly is true. As you guessed, there's a trigger on the Style that changes the control template.
<Window x:Class="SO_Xaml_ReadOnlyInputForm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly}"
Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<TextBlock Text="{TemplateBinding Text}"
Width="{TemplateBinding Width}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<CheckBox Grid.Row="0"
IsChecked="{Binding IsReadOnly}"
Content="Is Read-only?" />
<StackPanel Grid.Row="1"
Orientation="Horizontal">
<TextBlock>Item1</TextBlock>
<TextBox Text="{Binding Item1Text}"
Width="100" />
</StackPanel>
</Grid>
</Window>
VIEWMODEL class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Prism.ViewModel;
using Microsoft.Practices.Prism.Commands;
namespace SO_Xaml_ReadOnlyInputForm
{
public class ViewModel : NotificationObject
{
private string _itemText;
private bool _isReadOnly;
public string Item1Text
{
get { return _itemText; }
set
{
_itemText = value;
RaisePropertyChanged(() => Item1Text);
}
}
public bool IsReadOnly
{
get { return _isReadOnly; }
set
{
_isReadOnly = value;
RaisePropertyChanged(() => IsReadOnly);
}
}
public ViewModel()
{
Item1Text = "This is the text";
}
}
}

2 states click - listbox item WPF

I have a ListboxItem with a checkbox in its template. When I click the checkbox, a section of the template gets visible. That works ok.
I am trying to simulate the same behaviour by clicking the item itself making it expand/collapse the respective section. It should always negate the current state of the item(expanded/collapsed)
I am using C#/WPF
<Grid x:Name="gridExpanded"
HorizontalAlignment="Stretch"
Margin="8"
Grid.RowSpan="1"
Width="Auto"
Height="Auto"
Visibility="{Binding IsChecked, Converter={StaticResource booleanToVisibilityConverter}, ElementName=checkBox}" />
It sounds like you are actually looking for the Expander control. This allows you to specify a header and content, and clicking on the header will toggle the visibility of the content
By WPF ListBox does not change CheckBox state when the corresponding label is clicked.
To solve this,
1) Add a IsVisibleFlag property to the item model
2) Add a handler for the PreviewMouseLeftButtonDown event of the item
3) In the handler use ItemContainerGenerator.ContainerFromItem to update the visibility flag on click
4) Associate the visibility of your template section with the IsVisibleFlag (or with the checkBox state).
The ItemModel:
publibc class MyItemModel : INotifyPropertyChanged
{
private bool _isVisibleFlag;
public bool IsVisibleFlag
{
get { return _isVisibleFlag; }
set
{
if (_isVisibleFlag != value)
{
_isVisibleFlag = value;
OnPropertyChanged(() => IsVisibleFlag);
}
}
}
// ItemText property goes here (I ommited it to save space)
}
In XAML:
<Window
<!--generated x:Class and xmlns goes here (I ommited them to save space) -->
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<Window.Resources>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListViewItem_PreviewMouseLeftButtonDown" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox Name="chkVisible" Grid.Column="0" IsChecked="{Binding IsVisibleFlag}" />
<TextBlock Grid.Column="1" Text="{Binding ItemText}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ListBox Name="MyListBox" ItemsSource="{Binding AddableWidgets}" />
</Grid>
</Window>
In code:
private void ListViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < MyListBox.Items.Count; i++)
{
object yourObject = MyListBox.Items[i];
ListBoxItem lbi = (ListBoxItem)MyListBox.ItemContainerGenerator.ContainerFromItem(yourObject);
if (lbi.IsFocused)
{
MyItemModel w = (MyItemModel)MyListBox.Items[i];
w.IsVisibleFlag = !w.IsVisibleFlag;
e.Handled = true;
}
}
}

How do I trigger a WPF expander IsExpanded property from another expander's IsExpanded property

I have two expanders, side by side. I want only one to be expanded at a time. So if one is expanded, and the user expands the other, I want the first one to collapse. The user can have both collapsed, and both collapsed is the starting state.
As can be seen in the code, I have included the "Header" property as a test, and it works as expected, but the IsExpanded property is not working.
<Expander x:Name="emailExpander">
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="IsExpanded" Value="False"/>
<Setter Property="Header" Value="Email"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanded,ElementName=customerExpander}" Value="True">
<Setter Property="IsExpanded" Value="False"/>
<Setter Property="Header" Value="other expanded"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
</Expander>
This can be handled by binding to a view object with a little logic added.
In your WPF bind the IsExpanded property to the EmailExpanded and CustomerExpanded properties of the view.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Expander Grid.Column="0" Header="Email" IsExpanded="{Binding EmailExpanded}">
<TextBlock Text="Email Data"/>
</Expander>
<Expander Grid.Column="1" Header="Customer" IsExpanded="{Binding CustomerExpanded}">
<TextBlock Text="Customer Data"/>
</Expander>
</Grid>
Assign the view in your main Window.
public MainWindow()
{
InitializeComponent();
DataContext = new View();
}
Then make your view class something like the following.
class View : INotifyPropertyChanged
{
private bool _CustomerExpanded;
public bool CustomerExpanded
{
get
{
return _CustomerExpanded;
}
set
{
if (_CustomerExpanded != value)
{
// Add logic to close Email Expander
if (value)
{
EmailExpanded = false;
}
_CustomerExpanded = value;
OnPropertyChanged("CustomerExpanded");
}
}
}
private bool _EmailExpanded;
public bool EmailExpanded
{
get
{
return _EmailExpanded;
}
set
{
if (_EmailExpanded != value)
{
// Add logic to close Customer Expander
if (value)
{
CustomerExpanded = false;
}
_EmailExpanded = value;
OnPropertyChanged("EmailExpanded");
}
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Notice the addition to the setters. Collapsing an expander will have no effect on the other expander, but expanding one will cause the other to collapse. No stack overflow :)
I found the answer in this post:
WPF Expanders Triggers
Use BoolInverterConverter in the answer above and here is the code snippets for your case
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:BoolInverterConverter x:Key="bic"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Expander x:Name="emailExpander" IsExpanded="{Binding ElementName=customerExpander, Converter={StaticResource bic}, Path=IsExpanded}">
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="Header" Value="Email"/>
</Style>
</Expander.Style>
<StackPanel Margin="10,4,0,0">
<CheckBox Margin="4" Content="Email 1" />
<CheckBox Margin="4" Content="Email 2" />
<CheckBox Margin="4" Content="Email 3" />
</StackPanel>
</Expander>
<Expander x:Name="customerExpander" Grid.Column="1">
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="Header" Value="Customer"/>
</Style>
</Expander.Style>
<StackPanel Margin="10,4,0,0">
<CheckBox Margin="4" Content="Customer 1" />
<CheckBox Margin="4" Content="Customer 2" />
<CheckBox Margin="4" Content="Customer 3" />
</StackPanel>
</Expander>
</Grid>
What you're better off doing is use an accordion control released in the WPF Toolkit V2. Very handy and no "Stack Overflow" exceptions. =)

Resources