Wave 9: Form args, alpha move, search, extra slots, move editors

PKMEditor:
- Form Argument control (IFormArgument, conditional NumericUpDown)
- Alpha Mastered Move selector (PA8/Legends Arceus)
- Move Shop editor button (IMoveShop8Mastery)
- Tech Records editor button (ITechRecord)
- Nickname font warning button

SAV Editor:
- Box search bar with species name filtering and slot dimming
- Extra slots display in Other tab (Daycare, Fused, Ride, etc.)
- Save slot info display (type, generation, checksum status)
- Slot search opacity and highlight model support
This commit is contained in:
montanon 2026-03-16 10:17:09 -03:00
parent b223d7b1c8
commit f76c98bfc0
6 changed files with 380 additions and 29 deletions

View File

@ -24,6 +24,20 @@ public partial class SlotModel : ObservableObject
[ObservableProperty]
private bool _isEmpty = true;
/// <summary>Whether the slot matches the current search filter.</summary>
[ObservableProperty]
private bool _isHighlighted;
/// <summary>Whether a search filter is currently active.</summary>
[ObservableProperty]
private bool _isSearchActive;
/// <summary>Opacity to use when a search is active (1.0 if highlighted or no search, 0.3 if not matching).</summary>
public double SearchOpacity => IsHighlighted ? 1.0 : (_isSearchActive ? 0.3 : 1.0);
partial void OnIsHighlightedChanged(bool value) => OnPropertyChanged(nameof(SearchOpacity));
partial void OnIsSearchActiveChanged(bool value) => OnPropertyChanged(nameof(SearchOpacity));
/// <summary>The PKM entity backing this slot, if any.</summary>
[ObservableProperty]
private PKM? _entity;
@ -38,6 +52,10 @@ public partial class SlotModel : ObservableObject
? $"{SpeciesName.GetSpeciesNameGeneration(Entity.Species, 2, Entity.Format)} Lv.{Entity.CurrentLevel}"
: "Empty";
/// <summary>Label describing the slot type (e.g. "Daycare", "Fused Kyurem").</summary>
[ObservableProperty]
private string _slotLabel = string.Empty;
partial void OnEntityChanged(PKM? value)
{
OnPropertyChanged(nameof(ShowdownText));

View File

@ -11,6 +11,8 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PKHeX.Avalonia.Converters;
using PKHeX.Avalonia.ViewModels.Subforms;
using PKHeX.Avalonia.Views.Subforms;
using PKHeX.Core;
using PKHeX.Drawing.PokeSprite.Avalonia;
using SkiaSharp;
@ -165,6 +167,19 @@ public partial class PKMEditorViewModel : ObservableObject
[ObservableProperty] private bool _isShadow;
[ObservableProperty] private bool _hasShadow;
// Form Argument
[ObservableProperty] private uint _formArgument;
[ObservableProperty] private bool _hasFormArgument;
[ObservableProperty] private uint _formArgumentMax;
// Alpha Mastered Move (Gen 8a - Legends Arceus)
[ObservableProperty] private ComboItem? _selectedAlphaMove;
[ObservableProperty] private bool _hasAlphaMove;
// Move Shop / Tech Record visibility
[ObservableProperty] private bool _hasMoveShop;
[ObservableProperty] private bool _hasTechRecords;
// Gen-specific: Catch Rate (Gen 1)
[ObservableProperty] private int _catchRate;
[ObservableProperty] private bool _hasCatchRate;
@ -950,6 +965,41 @@ public void PopulateFields(PKM pk)
PokeStarFame = 0;
}
// Form Argument
if (pk is IFormArgument fa)
{
HasFormArgument = true;
FormArgument = fa.FormArgument;
FormArgumentMax = FormArgumentUtil.GetFormArgumentMax(pk.Species, pk.Form, pk.Context);
if (FormArgumentMax == 0)
FormArgumentMax = 255;
}
else
{
HasFormArgument = false;
FormArgument = 0;
FormArgumentMax = 255;
}
// Alpha Mastered Move (Legends Arceus)
if (pk is PA8 pa8)
{
HasAlphaMove = true;
var alphaMoveId = pa8.AlphaMove;
SelectedAlphaMove = MoveList.FirstOrDefault(m => m.Value == alphaMoveId);
}
else
{
HasAlphaMove = false;
SelectedAlphaMove = null;
}
// Move Shop (Legends Arceus)
HasMoveShop = pk is IMoveShop8Mastery;
// Tech Records (Gen 8+)
HasTechRecords = pk is ITechRecord;
// Origin Mark indicator
var gen = pk.Generation;
HasOriginMark = gen >= 3;
@ -1231,9 +1281,51 @@ public void PopulateFields(PKM pk)
pb7Save.Mood = (byte)Math.Clamp(Mood7b, 0, 255);
}
// Form Argument
if (Entity is IFormArgument faSave)
faSave.FormArgument = FormArgument;
// Alpha Mastered Move (Legends Arceus)
if (Entity is PA8 pa8Save && SelectedAlphaMove is not null)
pa8Save.AlphaMove = (ushort)SelectedAlphaMove.Value;
return Entity;
}
// --- Move Shop / Tech Record editor commands ---
[RelayCommand]
private async Task OpenMoveShop()
{
if (Entity is not IMoveShop8Mastery master || Entity is not IMoveShop8 shop) return;
try
{
PreparePKM();
var vm = new MoveShopEditorViewModel(shop, master, Entity);
var view = new MoveShopEditorView { DataContext = vm };
var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
if (mainWindow != null)
await view.ShowDialog(mainWindow);
}
catch (Exception ex) { LegalityReport = $"Move Shop error: {ex.Message}"; }
}
[RelayCommand]
private async Task OpenTechRecords()
{
if (Entity is not ITechRecord tr) return;
try
{
PreparePKM();
var vm = new TechRecordEditorViewModel(tr, Entity);
var view = new TechRecordEditorView { DataContext = vm };
var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
if (mainWindow != null)
await view.ShowDialog(mainWindow);
}
catch (Exception ex) { LegalityReport = $"Tech Record error: {ex.Message}"; }
}
// --- IV/EV quick-set commands ---
[RelayCommand]
@ -1402,6 +1494,46 @@ private string GetNatureColor(int statIndex)
};
}
// --- Nickname warning ---
[RelayCommand]
private void ShowNicknameWarning()
{
if (Entity is null) return;
var nickname = Nickname ?? string.Empty;
var species = Entity.Species;
var lang = Entity.Language;
var defaultName = SpeciesName.GetSpeciesNameGeneration(species, lang, Entity.Format);
// Check for non-ASCII characters that might not render in older games
bool hasSpecialChars = false;
foreach (var c in nickname)
{
if (c > 0x7E || c < 0x20)
{
hasSpecialChars = true;
break;
}
}
if (string.IsNullOrEmpty(nickname))
{
LegalityReport = "Nickname is empty.";
}
else if (nickname == defaultName)
{
LegalityReport = $"Nickname matches default species name: '{defaultName}'.";
}
else if (hasSpecialChars)
{
LegalityReport = $"Nickname '{nickname}' contains special characters that may not render correctly in-game. Default name: '{defaultName}'.";
}
else
{
LegalityReport = $"Nickname '{nickname}' appears to use standard characters. Default name: '{defaultName}'.";
}
}
// --- Sprite context menu commands ---
[RelayCommand]

View File

@ -127,6 +127,18 @@ public void Redo()
[ObservableProperty]
private Bitmap? _boxWallpaper;
// Box search/filter
[ObservableProperty] private string _searchText = string.Empty;
[ObservableProperty] private string _searchResultText = string.Empty;
// Extra slots (Other tab)
public ObservableCollection<SlotModel> ExtraSlots { get; } = [];
[ObservableProperty] private bool _hasExtraSlots;
// Save slot info
[ObservableProperty] private string _saveSlotInfo = string.Empty;
[ObservableProperty] private bool _hasSaveSlotInfo;
public ObservableCollection<SlotModel> BoxSlots { get; } = [];
public ObservableCollection<SlotModel> PartySlots { get; } = [];
public ObservableCollection<string> BoxNames { get; } = [];
@ -181,6 +193,9 @@ public void LoadSaveFile(SaveFile sav)
RefreshParty();
UpdateToolVisibility(sav);
RefreshDaycare(sav);
RefreshExtraSlots(sav);
RefreshSaveSlotInfo(sav);
SearchText = string.Empty;
}
private void UpdateToolVisibility(SaveFile sav)
@ -455,6 +470,125 @@ private void RefreshDaycare(SaveFile sav)
}
}
#region Box Search
partial void OnSearchTextChanged(string value)
{
if (_sav is null) return;
var searchLower = value.ToLowerInvariant().Trim();
var isActive = !string.IsNullOrEmpty(searchLower);
int count = 0;
foreach (var slot in BoxSlots)
{
slot.IsSearchActive = isActive;
if (slot.Entity is null || slot.Entity.Species == 0)
{
slot.IsHighlighted = false;
continue;
}
if (!isActive)
{
slot.IsHighlighted = false;
continue;
}
var name = SpeciesName.GetSpeciesNameGeneration(slot.Entity.Species, 2, slot.Entity.Format).ToLowerInvariant();
var matches = name.Contains(searchLower);
slot.IsHighlighted = matches;
if (matches) count++;
}
SearchResultText = isActive ? $"{count} found" : string.Empty;
}
#endregion
#region Extra Slots
private void RefreshExtraSlots(SaveFile sav)
{
ExtraSlots.Clear();
try
{
var extras = sav.GetExtraSlots(true);
if (extras.Count == 0)
{
HasExtraSlots = false;
return;
}
HasExtraSlots = true;
foreach (var slotInfo in extras)
{
var pk = slotInfo.Read(sav);
var model = new SlotModel
{
Slot = slotInfo.Slot,
SlotLabel = GetSlotTypeLabel(slotInfo.Type, slotInfo.Slot),
};
model.Entity = pk;
if (pk.Species == 0)
{
model.SetImage(SpriteUtil.Spriter.None);
model.IsEmpty = true;
}
else
{
var sprite = pk.Sprite();
model.SetImage(sprite);
model.IsEmpty = false;
}
ExtraSlots.Add(model);
}
}
catch
{
HasExtraSlots = false;
}
}
private static string GetSlotTypeLabel(StorageSlotType type, int slot) => type switch
{
StorageSlotType.Daycare => $"Daycare #{slot + 1}",
StorageSlotType.GTS => "GTS Upload",
StorageSlotType.BattleBox => $"Battle Box #{slot + 1}",
StorageSlotType.FusedKyurem => "Fused Kyurem",
StorageSlotType.FusedNecrozmaS => "Fused Necrozma (Solgaleo)",
StorageSlotType.FusedNecrozmaM => "Fused Necrozma (Lunala)",
StorageSlotType.FusedCalyrex => "Fused Calyrex",
StorageSlotType.Resort => $"Resort #{slot + 1}",
StorageSlotType.Ride => "Ride Legend",
StorageSlotType.BattleAgency => $"Battle Agency #{slot + 1}",
StorageSlotType.SurpriseTrade => $"Surprise Trade #{slot + 1}",
StorageSlotType.Underground => $"Underground #{slot + 1}",
StorageSlotType.Scripted => $"Scripted #{slot + 1}",
StorageSlotType.PGL => "PGL Upload",
StorageSlotType.Pokéwalker => "Pokewalker",
StorageSlotType.Shiny => $"Shiny Cache #{slot + 1}",
_ => $"{type} #{slot + 1}",
};
#endregion
#region Save Slot Info
private void RefreshSaveSlotInfo(SaveFile sav)
{
var sb = new StringBuilder();
sb.AppendLine($"Type: {sav.GetType().Name}");
sb.AppendLine($"Generation: {sav.Generation}");
sb.AppendLine($"Version: {sav.Version}");
sb.AppendLine($"Boxes: {sav.BoxCount} ({sav.BoxSlotCount} slots each)");
if (sav.HasParty)
sb.AppendLine($"Party: {sav.PartyCount} Pokemon");
sb.AppendLine($"Checksums: {(sav.ChecksumsValid ? "Valid" : "Invalid")}");
SaveSlotInfo = sb.ToString().TrimEnd();
HasSaveSlotInfo = true;
}
#endregion
#region Box Management (Sort / Clear)
[RelayCommand]

View File

@ -69,7 +69,12 @@
<!-- Nickname -->
<TextBlock Grid.Row="2" Grid.Column="0" Text="Nickname:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Nickname}" Height="25" Width="144" HorizontalAlignment="Left" />
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<TextBox Text="{Binding Nickname}" Height="25" Width="144" />
<Button Content="?" Command="{Binding ShowNicknameWarningCommand}"
Width="20" Height="20" Padding="0" FontSize="10" Margin="2,0,0,0"
ToolTip.Tip="Check nickname font compatibility" />
</StackPanel>
<!-- EXP + Level -->
<TextBlock Grid.Row="3" Grid.Column="0" Text="EXP:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
@ -134,6 +139,12 @@
<!-- N's Sparkle (Gen 5) -->
<CheckBox Grid.Row="14" Grid.Column="1" Content="N's Sparkle" IsChecked="{Binding NSparkle}"
IsVisible="{Binding HasNSparkle}" VerticalAlignment="Center" />
<!-- Form Argument -->
<TextBlock Grid.Row="15" Grid.Column="0" Text="Form Arg:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
IsVisible="{Binding HasFormArgument}" />
<NumericUpDown Grid.Row="15" Grid.Column="1" Value="{Binding FormArgument}" Minimum="0" Maximum="{Binding FormArgumentMax}" Height="25" Width="80" HorizontalAlignment="Left"
IsVisible="{Binding HasFormArgument}" />
</Grid>
</TabItem>
@ -349,33 +360,50 @@
<TextBlock Text="Moves" VerticalAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid Margin="8" RowDefinitions="27,27,27,27,27" ColumnDefinitions="52,*,18">
<TextBlock Grid.Row="0" Grid.ColumnSpan="3" Text="Current Moves" FontWeight="SemiBold" VerticalAlignment="Center" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Move 1:" VerticalAlignment="Center" />
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedMove1}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" />
<TextBlock Grid.Row="1" Grid.Column="2" Text="!" Foreground="Red" FontWeight="Bold"
IsVisible="{Binding !Move1Legal}" VerticalAlignment="Center" HorizontalAlignment="Center"
ToolTip.Tip="Illegal move" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Move 2:" VerticalAlignment="Center" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedMove2}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" />
<TextBlock Grid.Row="2" Grid.Column="2" Text="!" Foreground="Red" FontWeight="Bold"
IsVisible="{Binding !Move2Legal}" VerticalAlignment="Center" HorizontalAlignment="Center"
ToolTip.Tip="Illegal move" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Move 3:" VerticalAlignment="Center" />
<ComboBox Grid.Row="3" Grid.Column="1" ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedMove3}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" />
<TextBlock Grid.Row="3" Grid.Column="2" Text="!" Foreground="Red" FontWeight="Bold"
IsVisible="{Binding !Move3Legal}" VerticalAlignment="Center" HorizontalAlignment="Center"
ToolTip.Tip="Illegal move" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Move 4:" VerticalAlignment="Center" />
<ComboBox Grid.Row="4" Grid.Column="1" ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedMove4}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" />
<TextBlock Grid.Row="4" Grid.Column="2" Text="!" Foreground="Red" FontWeight="Bold"
IsVisible="{Binding !Move4Legal}" VerticalAlignment="Center" HorizontalAlignment="Center"
ToolTip.Tip="Illegal move" />
</Grid>
<StackPanel Margin="8" Spacing="4">
<Grid RowDefinitions="27,27,27,27,27" ColumnDefinitions="52,*,18">
<TextBlock Grid.Row="0" Grid.ColumnSpan="3" Text="Current Moves" FontWeight="SemiBold" VerticalAlignment="Center" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Move 1:" VerticalAlignment="Center" />
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedMove1}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" />
<TextBlock Grid.Row="1" Grid.Column="2" Text="!" Foreground="Red" FontWeight="Bold"
IsVisible="{Binding !Move1Legal}" VerticalAlignment="Center" HorizontalAlignment="Center"
ToolTip.Tip="Illegal move" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Move 2:" VerticalAlignment="Center" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedMove2}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" />
<TextBlock Grid.Row="2" Grid.Column="2" Text="!" Foreground="Red" FontWeight="Bold"
IsVisible="{Binding !Move2Legal}" VerticalAlignment="Center" HorizontalAlignment="Center"
ToolTip.Tip="Illegal move" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Move 3:" VerticalAlignment="Center" />
<ComboBox Grid.Row="3" Grid.Column="1" ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedMove3}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" />
<TextBlock Grid.Row="3" Grid.Column="2" Text="!" Foreground="Red" FontWeight="Bold"
IsVisible="{Binding !Move3Legal}" VerticalAlignment="Center" HorizontalAlignment="Center"
ToolTip.Tip="Illegal move" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Move 4:" VerticalAlignment="Center" />
<ComboBox Grid.Row="4" Grid.Column="1" ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedMove4}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" />
<TextBlock Grid.Row="4" Grid.Column="2" Text="!" Foreground="Red" FontWeight="Bold"
IsVisible="{Binding !Move4Legal}" VerticalAlignment="Center" HorizontalAlignment="Center"
ToolTip.Tip="Illegal move" />
</Grid>
<!-- Alpha Mastered Move (Legends Arceus) -->
<StackPanel IsVisible="{Binding HasAlphaMove}" Spacing="4" Margin="0,4,0,0">
<TextBlock Text="Alpha Mastered Move" FontWeight="SemiBold" />
<ComboBox ItemsSource="{Binding MoveList}" SelectedItem="{Binding SelectedAlphaMove}"
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="200" HorizontalAlignment="Left" />
</StackPanel>
<!-- Move Shop / Tech Records buttons -->
<StackPanel Orientation="Horizontal" Spacing="4" Margin="0,4,0,0">
<Button Content="Move Shop" Command="{Binding OpenMoveShopCommand}" Padding="6,2" FontSize="10"
IsVisible="{Binding HasMoveShop}" />
<Button Content="Tech Records" Command="{Binding OpenTechRecordsCommand}" Padding="6,2" FontSize="10"
IsVisible="{Binding HasTechRecords}" />
</StackPanel>
</StackPanel>
</TabItem>
<!-- Cosmetic Tab -->

View File

@ -9,6 +9,13 @@
<!-- Box Tab -->
<TabItem Header="Box">
<DockPanel Margin="2">
<!-- Search bar -->
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,Auto" Margin="0,2,0,2"
IsVisible="{Binding IsLoaded}">
<TextBox Text="{Binding SearchText}" Watermark="Search species..." Height="24" FontSize="11" Margin="0,0,4,0" />
<TextBlock Grid.Column="1" Text="{Binding SearchResultText}" FontSize="10" VerticalAlignment="Center" Opacity="0.7" />
</Grid>
<!-- Box Navigation: [<<] ComboBox [>>] -->
<Grid DockPanel.Dock="Top" ColumnDefinitions="30,*,30" Margin="0,2,0,4">
<Button Grid.Column="0" Content="&lt;&lt;" Command="{Binding PreviousBoxCommand}"
@ -88,12 +95,43 @@
<TabItem Header="Other">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="4" Spacing="4">
<!-- Daycare -->
<TextBlock Text="Daycare" FontWeight="SemiBold" FontSize="11" IsVisible="{Binding HasDaycare}" />
<TextBlock Text="{Binding DaycareInfo}" FontSize="11" FontFamily="Consolas,monospace"
IsVisible="{Binding HasDaycare}" />
<!-- Extra Slots (Fused, GTS, Battle Box, etc.) -->
<Border Height="1" Background="#CCCCCC" Margin="0,4" IsVisible="{Binding HasExtraSlots}" />
<TextBlock Text="Extra Slots" FontWeight="SemiBold" FontSize="11" IsVisible="{Binding HasExtraSlots}" />
<ItemsControl ItemsSource="{Binding ExtraSlots}" IsVisible="{Binding HasExtraSlots}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,1" Padding="2" CornerRadius="2"
Background="#08000000">
<StackPanel Orientation="Horizontal" Spacing="6">
<Image Source="{Binding Image}" Width="40" Height="30"
RenderOptions.BitmapInterpolationMode="None"
VerticalAlignment="Center" />
<StackPanel VerticalAlignment="Center" Spacing="0">
<TextBlock Text="{Binding SlotLabel}" FontSize="10" FontWeight="SemiBold" />
<TextBlock Text="{Binding Summary}" FontSize="10" Opacity="0.7" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Save Slot Info -->
<Border Height="1" Background="#CCCCCC" Margin="0,4" IsVisible="{Binding HasSaveSlotInfo}" />
<TextBlock Text="Save Info" FontWeight="SemiBold" FontSize="11" IsVisible="{Binding HasSaveSlotInfo}" />
<TextBlock Text="{Binding SaveSlotInfo}" FontSize="10" FontFamily="Consolas,monospace"
IsVisible="{Binding HasSaveSlotInfo}" />
<!-- Fallback -->
<TextBlock Text="No additional slots available for this save type."
FontSize="11" Opacity="0.6"
IsVisible="{Binding !HasDaycare}" />
IsVisible="{Binding !IsLoaded}" />
</StackPanel>
</ScrollViewer>
</TabItem>

View File

@ -32,6 +32,7 @@
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="1" CornerRadius="0"
Cursor="Hand"
Opacity="{Binding SearchOpacity}"
DragDrop.AllowDrop="True"
AutomationProperties.Name="{Binding Summary}">
<ToolTip.Tip>