Esportazione avanzata e UI migliorata
Aggiunta esportazione dati in JSON/XML oltre a CSV, con nome file personalizzabile (prefisso, suffisso, formato data, anteprima). Nuove impostazioni per esportazione e preview dinamica. Gestione sorgente corse cavalli da API o CSV Punters, con aggregazione automatica. Migliorati stili DataGrid e numerazione righe. Rimossa pagina info, stili globali consolidati, ComboBox più leggibili. Migliorata gestione errori e messaggi utente.
This commit is contained in:
@@ -153,6 +153,7 @@ namespace HorseRacingPredictor.Football.Database
|
|||||||
l.country AS Paese,
|
l.country AS Paese,
|
||||||
l.name AS Campionato,
|
l.name AS Campionato,
|
||||||
f.date AS [Data / Ora],
|
f.date AS [Data / Ora],
|
||||||
|
f.timestamp AS unix_ts,
|
||||||
f.status AS Stato,
|
f.status AS Stato,
|
||||||
th.name AS Casa,
|
th.name AS Casa,
|
||||||
ta.name AS Trasferta,
|
ta.name AS Trasferta,
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
Loaded="Window_Loaded">
|
Loaded="Window_Loaded">
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<!-- =========================================================
|
<!-- Inlined Global Styles (avoids relying on external resource resolution at runtime) -->
|
||||||
CATPPUCCIN MOCHA PALETTE
|
<!-- CATPPUCCIN MOCHA PALETTE (shared) -->
|
||||||
========================================================= -->
|
|
||||||
<Color x:Key="CBase">#1E1E2E</Color>
|
<Color x:Key="CBase">#1E1E2E</Color>
|
||||||
<Color x:Key="CMantle">#181825</Color>
|
<Color x:Key="CMantle">#181825</Color>
|
||||||
<Color x:Key="CCrust">#11111B</Color>
|
<Color x:Key="CCrust">#11111B</Color>
|
||||||
@@ -41,9 +40,21 @@
|
|||||||
<SolidColorBrush x:Key="BrPeach" Color="{StaticResource CPeach}"/>
|
<SolidColorBrush x:Key="BrPeach" Color="{StaticResource CPeach}"/>
|
||||||
<SolidColorBrush x:Key="BrLavender" Color="{StaticResource CLavender}"/>
|
<SolidColorBrush x:Key="BrLavender" Color="{StaticResource CLavender}"/>
|
||||||
|
|
||||||
<!-- =========================================================
|
<!-- Force ComboBox text to black for readability on light backgrounds -->
|
||||||
NAV BUTTON STYLE (icon-only sidebar)
|
<Style TargetType="ComboBox">
|
||||||
========================================================= -->
|
<Setter Property="Foreground" Value="Black"/>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="ComboBoxItem">
|
||||||
|
<Setter Property="Foreground" Value="Black"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Acrylic-like background (semi-transparent fallback) -->
|
||||||
|
<SolidColorBrush x:Key="AcrylicBackgroundBrush" Color="#0F000000"/>
|
||||||
|
|
||||||
|
<!-- Subtle shadow effect for elevation -->
|
||||||
|
<DropShadowEffect x:Key="SubtleDropShadow" BlurRadius="12" ShadowDepth="2" Color="#50000000"/>
|
||||||
|
|
||||||
|
<!-- NAV BUTTON STYLE (icon-only sidebar) -->
|
||||||
<Style x:Key="NavBtn" TargetType="RadioButton">
|
<Style x:Key="NavBtn" TargetType="RadioButton">
|
||||||
<Setter Property="Width" Value="48"/>
|
<Setter Property="Width" Value="48"/>
|
||||||
<Setter Property="Height" Value="48"/>
|
<Setter Property="Height" Value="48"/>
|
||||||
@@ -72,9 +83,7 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- =========================================================
|
<!-- ACCENT BUTTON -->
|
||||||
ACCENT BUTTON
|
|
||||||
========================================================= -->
|
|
||||||
<Style x:Key="AccentBtn" TargetType="Button">
|
<Style x:Key="AccentBtn" TargetType="Button">
|
||||||
<Setter Property="Foreground" Value="#181825"/>
|
<Setter Property="Foreground" Value="#181825"/>
|
||||||
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
|
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
|
||||||
@@ -102,9 +111,7 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- =========================================================
|
<!-- FLAT TEXTBOX -->
|
||||||
FLAT TEXTBOX
|
|
||||||
========================================================= -->
|
|
||||||
<Style x:Key="FlatTb" TargetType="TextBox">
|
<Style x:Key="FlatTb" TargetType="TextBox">
|
||||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||||
@@ -134,9 +141,7 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- =========================================================
|
<!-- PASSWORD BOX -->
|
||||||
PASSWORD BOX
|
|
||||||
========================================================= -->
|
|
||||||
<Style x:Key="FlatPb" TargetType="PasswordBox">
|
<Style x:Key="FlatPb" TargetType="PasswordBox">
|
||||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||||
@@ -166,9 +171,7 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- =========================================================
|
<!-- PROGRESS BAR -->
|
||||||
PROGRESS BAR
|
|
||||||
========================================================= -->
|
|
||||||
<Style x:Key="ModernPb" TargetType="ProgressBar">
|
<Style x:Key="ModernPb" TargetType="ProgressBar">
|
||||||
<Setter Property="Height" Value="4"/>
|
<Setter Property="Height" Value="4"/>
|
||||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||||
@@ -189,9 +192,7 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- =========================================================
|
<!-- DATAGRID -->
|
||||||
DATAGRID
|
|
||||||
========================================================= -->
|
|
||||||
<Style x:Key="ModernDg" TargetType="DataGrid">
|
<Style x:Key="ModernDg" TargetType="DataGrid">
|
||||||
<Setter Property="Background" Value="#282A3A"/>
|
<Setter Property="Background" Value="#282A3A"/>
|
||||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||||
@@ -211,6 +212,7 @@
|
|||||||
<Setter Property="FontFamily" Value="Segoe UI"/>
|
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||||
<Setter Property="FontSize" Value="13"/>
|
<Setter Property="FontSize" Value="13"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="DataGridColumnHeader">
|
<Style TargetType="DataGridColumnHeader">
|
||||||
<Setter Property="Background" Value="#23243A"/>
|
<Setter Property="Background" Value="#23243A"/>
|
||||||
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
||||||
@@ -220,6 +222,7 @@
|
|||||||
<Setter Property="BorderBrush" Value="#37394E"/>
|
<Setter Property="BorderBrush" Value="#37394E"/>
|
||||||
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="DataGridCell">
|
<Style TargetType="DataGridCell">
|
||||||
<Setter Property="Padding" Value="8,6"/>
|
<Setter Property="Padding" Value="8,6"/>
|
||||||
<Setter Property="BorderThickness" Value="0"/>
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
@@ -239,6 +242,7 @@
|
|||||||
</Trigger>
|
</Trigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="DataGridRow">
|
<Style TargetType="DataGridRow">
|
||||||
<Setter Property="Margin" Value="0"/>
|
<Setter Property="Margin" Value="0"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
@@ -247,6 +251,14 @@
|
|||||||
</Trigger>
|
</Trigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- Card style for lists / rows -->
|
||||||
|
<Style x:Key="CardStyle" TargetType="Border">
|
||||||
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
|
<Setter Property="Background" Value="#23232A"/>
|
||||||
|
<Setter Property="Padding" Value="12"/>
|
||||||
|
<Setter Property="Margin" Value="6"/>
|
||||||
|
</Style>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<!-- ================================================================
|
<!-- ================================================================
|
||||||
@@ -288,12 +300,7 @@
|
|||||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="20"
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="20"
|
||||||
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
|
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
<RadioButton x:Name="navInfo" Style="{StaticResource NavBtn}"
|
<!-- Info tab removed -->
|
||||||
ToolTip="Informazioni"
|
|
||||||
Checked="navInfo_Checked">
|
|
||||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="20"
|
|
||||||
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
|
|
||||||
</RadioButton>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -313,28 +320,29 @@
|
|||||||
<Grid Background="{StaticResource BrBase}">
|
<Grid Background="{StaticResource BrBase}">
|
||||||
|
|
||||||
<!-- ==== PAGE: FOOTBALL ==== -->
|
<!-- ==== PAGE: FOOTBALL ==== -->
|
||||||
<Grid x:Name="pageFootball" Margin="24,16">
|
<Grid x:Name="pageFootball" Margin="24,16" Panel.ZIndex="10">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<!-- Toolbar -->
|
<!-- Toolbar -->
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12">
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12" Panel.ZIndex="20">
|
||||||
<DatePicker x:Name="dpFootball" Width="160"
|
<DatePicker x:Name="dpFootball" Width="160"
|
||||||
Background="{StaticResource BrSurface0}"
|
Background="{StaticResource BrSurface0}"
|
||||||
Foreground="{StaticResource BrText}"
|
Foreground="{StaticResource BrText}"
|
||||||
FontSize="13" VerticalContentAlignment="Center"/>
|
FontSize="13" VerticalContentAlignment="Center" IsHitTestVisible="True"/>
|
||||||
<Button x:Name="btnDownloadFb" Content="Scarica Partite"
|
<Button x:Name="btnDownloadFb" Content="Scarica Partite"
|
||||||
Style="{StaticResource AccentBtn}"
|
Style="{StaticResource AccentBtn}"
|
||||||
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
||||||
Click="btnDownloadFb_Click"/>
|
Click="btnDownloadFb_Click" IsHitTestVisible="True"/>
|
||||||
<Button x:Name="btnExportFbCsv" Content="Esporta CSV"
|
<Button x:Name="btnExportFbCsv" Content="Esporta CSV"
|
||||||
Style="{StaticResource AccentBtn}"
|
Style="{StaticResource AccentBtn}"
|
||||||
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
|
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
|
||||||
IsEnabled="False"
|
IsEnabled="False"
|
||||||
Click="btnExportFbCsv_Click"/>
|
Click="btnExportFbCsv_Click" IsHitTestVisible="True"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<StackPanel Grid.Row="1" Margin="0,0,0,8">
|
<StackPanel Grid.Row="1" Margin="0,0,0,8">
|
||||||
<ProgressBar x:Name="pbFootball" Style="{StaticResource ModernPb}" Margin="0,0,0,4"/>
|
<ProgressBar x:Name="pbFootball" Style="{StaticResource ModernPb}" Margin="0,0,0,4"/>
|
||||||
@@ -352,21 +360,35 @@
|
|||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<!-- Toolbar -->
|
<!-- Source selector: API or CSV -->
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12">
|
<StackPanel Grid.Row="0" Margin="0,0,0,12">
|
||||||
<ComboBox x:Name="cmbDay" Width="140"
|
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||||
Background="{StaticResource BrSurface0}"
|
<RadioButton x:Name="rbRcApi" Content="API" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center" GroupName="RcSource" Checked="rbRcSource_Checked"/>
|
||||||
Foreground="{StaticResource BrText}"
|
<RadioButton x:Name="rbRcCsv" Content="CSV (Punters)" Foreground="{StaticResource BrText}" VerticalAlignment="Center" Margin="16,0,0,0" GroupName="RcSource" Checked="rbRcSource_Checked"/>
|
||||||
FontSize="13" VerticalContentAlignment="Center"/>
|
</StackPanel>
|
||||||
<Button x:Name="btnDownloadRc" Content="Scarica Corse"
|
<StackPanel Orientation="Horizontal">
|
||||||
Style="{StaticResource AccentBtn}"
|
<!-- API controls -->
|
||||||
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
<ComboBox x:Name="cmbDay" Width="140"
|
||||||
Click="btnDownloadRc_Click"/>
|
Background="{StaticResource BrSurface0}"
|
||||||
<Button x:Name="btnExportRcCsv" Content="Esporta CSV"
|
Foreground="{StaticResource BrText}"
|
||||||
Style="{StaticResource AccentBtn}"
|
FontSize="13" VerticalContentAlignment="Center"/>
|
||||||
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
|
<Button x:Name="btnDownloadRc" Content="Scarica Corse"
|
||||||
IsEnabled="False"
|
Style="{StaticResource AccentBtn}"
|
||||||
Click="btnExportRcCsv_Click"/>
|
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
||||||
|
Click="btnDownloadRc_Click"/>
|
||||||
|
<!-- CSV controls -->
|
||||||
|
<Button x:Name="btnBrowseCsvRc" Content="Seleziona cartella CSV..."
|
||||||
|
Style="{StaticResource AccentBtn}"
|
||||||
|
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
||||||
|
Visibility="Collapsed"
|
||||||
|
Click="btnBrowseCsvRc_Click"/>
|
||||||
|
<!-- Export (always visible) -->
|
||||||
|
<Button x:Name="btnExportRcCsv" Content="Esporta"
|
||||||
|
Style="{StaticResource AccentBtn}"
|
||||||
|
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
|
||||||
|
IsEnabled="False"
|
||||||
|
Click="btnExportRcCsv_Click"/>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<StackPanel Grid.Row="1" Margin="0,0,0,8">
|
<StackPanel Grid.Row="1" Margin="0,0,0,8">
|
||||||
@@ -375,15 +397,13 @@
|
|||||||
FontSize="12" Foreground="{StaticResource BrSubtext0}"/>
|
FontSize="12" Foreground="{StaticResource BrSubtext0}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<!-- Grid -->
|
<!-- Grid -->
|
||||||
<DataGrid x:Name="dgRacing" Grid.Row="2" Style="{StaticResource ModernDg}"/>
|
<DataGrid x:Name="dgRacing" Grid.Row="2" Style="{StaticResource ModernDg}" AutoGeneratingColumn="dgRacing_AutoGeneratingColumn"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- ==== PAGE: SETTINGS ==== -->
|
<!-- ==== PAGE: SETTINGS ==== -->
|
||||||
<ScrollViewer x:Name="pageSettings" Visibility="Collapsed"
|
<ScrollViewer x:Name="pageSettings" Visibility="Collapsed"
|
||||||
VerticalScrollBarVisibility="Auto" Padding="24,16">
|
VerticalScrollBarVisibility="Auto" Padding="24,16">
|
||||||
<StackPanel MaxWidth="600" HorizontalAlignment="Left">
|
<StackPanel MaxWidth="600" HorizontalAlignment="Left">
|
||||||
|
|
||||||
<!-- FOOTBALL API -->
|
|
||||||
<TextBlock Text="Calcio — API" FontSize="16" FontFamily="Segoe UI Semibold"
|
<TextBlock Text="Calcio — API" FontSize="16" FontFamily="Segoe UI Semibold"
|
||||||
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
|
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
|
||||||
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
|
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
|
||||||
@@ -391,6 +411,39 @@
|
|||||||
<TextBlock Text="API Key (api-football)" Foreground="{StaticResource BrText}"
|
<TextBlock Text="API Key (api-football)" Foreground="{StaticResource BrText}"
|
||||||
FontSize="13" Margin="0,0,0,6"/>
|
FontSize="13" Margin="0,0,0,6"/>
|
||||||
<TextBox x:Name="txtApiKey" Style="{StaticResource FlatTb}"/>
|
<TextBox x:Name="txtApiKey" Style="{StaticResource FlatTb}"/>
|
||||||
|
<!-- File name now built from prefix + date + suffix -->
|
||||||
|
<Grid Margin="0,8,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="Prefisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
|
||||||
|
<TextBox x:Name="txtFbPrefix" Style="{StaticResource FlatTb}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="1" Margin="8,0,0,0">
|
||||||
|
<TextBlock Text="Suffisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
|
||||||
|
<TextBox x:Name="txtFbSuffix" Style="{StaticResource FlatTb}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||||
|
<CheckBox x:Name="chkFbIncludeDate" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center">Includi data</CheckBox>
|
||||||
|
<ComboBox x:Name="cmbFbDateFormat" Width="180" Margin="12,0,0,0" Background="{StaticResource BrSurface0}" Foreground="Black">
|
||||||
|
<ComboBoxItem Content="yyyy-MM-dd"/>
|
||||||
|
<ComboBoxItem Content="dd-MM-yyyy"/>
|
||||||
|
<ComboBoxItem Content="yyyyMMdd"/>
|
||||||
|
<ComboBoxItem Content="ddMMyyyy"/>
|
||||||
|
<ComboBoxItem Content="yyyy-MM-dd_HH-mm"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="Anteprima nome file:" Foreground="{StaticResource BrSubtext0}" Margin="0,8,0,4"/>
|
||||||
|
<TextBlock x:Name="txtFbPreview" FontFamily="Segoe UI" FontSize="13" Foreground="{StaticResource BrText}" />
|
||||||
|
<TextBlock Text="Formato esportazione" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,12,0,6"/>
|
||||||
|
<ComboBox x:Name="cmbFbFormat" Width="160" SelectedIndex="0" Background="{StaticResource BrSurface0}" Foreground="Black">
|
||||||
|
<ComboBoxItem Content="CSV"/>
|
||||||
|
<ComboBoxItem Content="JSON"/>
|
||||||
|
<ComboBoxItem Content="XML"/>
|
||||||
|
</ComboBox>
|
||||||
<TextBlock Text="Cartella esportazione CSV" Foreground="{StaticResource BrText}"
|
<TextBlock Text="Cartella esportazione CSV" Foreground="{StaticResource BrText}"
|
||||||
FontSize="13" Margin="0,14,0,6"/>
|
FontSize="13" Margin="0,14,0,6"/>
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -409,12 +462,45 @@
|
|||||||
|
|
||||||
<!-- HORSE RACING API -->
|
<!-- HORSE RACING API -->
|
||||||
<TextBlock Text="Corse Cavalli — API" FontSize="16" FontFamily="Segoe UI Semibold"
|
<TextBlock Text="Corse Cavalli — API" FontSize="16" FontFamily="Segoe UI Semibold"
|
||||||
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
|
Foreground="{StaticResource BrBlue}" Margin="0,32,0,8"/>
|
||||||
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
|
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="Username" Foreground="{StaticResource BrText}"
|
<TextBlock Text="Username" Foreground="{StaticResource BrText}"
|
||||||
FontSize="13" Margin="0,0,0,6"/>
|
FontSize="13" Margin="0,0,0,6"/>
|
||||||
<TextBox x:Name="txtRacingUser" Style="{StaticResource FlatTb}"/>
|
<TextBox x:Name="txtRacingUser" Style="{StaticResource FlatTb}"/>
|
||||||
|
<!-- File name now built from prefix + date + suffix -->
|
||||||
|
<Grid Margin="0,8,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="Prefisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
|
||||||
|
<TextBox x:Name="txtRcPrefix" Style="{StaticResource FlatTb}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="1" Margin="8,0,0,0">
|
||||||
|
<TextBlock Text="Suffisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
|
||||||
|
<TextBox x:Name="txtRcSuffix" Style="{StaticResource FlatTb}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||||
|
<CheckBox x:Name="chkRcIncludeDate" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center">Includi data</CheckBox>
|
||||||
|
<ComboBox x:Name="cmbRcDateFormat" Width="180" Margin="12,0,0,0" Background="{StaticResource BrSurface0}" Foreground="Black">
|
||||||
|
<ComboBoxItem Content="yyyy-MM-dd"/>
|
||||||
|
<ComboBoxItem Content="dd-MM-yyyy"/>
|
||||||
|
<ComboBoxItem Content="yyyyMMdd"/>
|
||||||
|
<ComboBoxItem Content="ddMMyyyy"/>
|
||||||
|
<ComboBoxItem Content="yyyy-MM-dd_HH-mm"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="Anteprima nome file:" Foreground="{StaticResource BrSubtext0}" Margin="0,8,0,4"/>
|
||||||
|
<TextBlock x:Name="txtRcPreview" FontFamily="Segoe UI" FontSize="13" Foreground="{StaticResource BrText}" />
|
||||||
|
<TextBlock Text="Formato esportazione" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,12,0,6"/>
|
||||||
|
<ComboBox x:Name="cmbRcFormat" Width="160" SelectedIndex="0" Background="{StaticResource BrSurface0}" Foreground="Black">
|
||||||
|
<ComboBoxItem Content="CSV"/>
|
||||||
|
<ComboBoxItem Content="JSON"/>
|
||||||
|
<ComboBoxItem Content="XML"/>
|
||||||
|
</ComboBox>
|
||||||
<TextBlock Text="Password" Foreground="{StaticResource BrText}"
|
<TextBlock Text="Password" Foreground="{StaticResource BrText}"
|
||||||
FontSize="13" Margin="0,14,0,6"/>
|
FontSize="13" Margin="0,14,0,6"/>
|
||||||
<PasswordBox x:Name="txtRacingPass" Style="{StaticResource FlatPb}"/>
|
<PasswordBox x:Name="txtRacingPass" Style="{StaticResource FlatPb}"/>
|
||||||
@@ -435,7 +521,7 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- SAVE -->
|
<!-- SAVE -->
|
||||||
<Button Content="Salva impostazioni"
|
<Button x:Name="btnSaveSettings" Content="Salva Impostazioni"
|
||||||
Style="{StaticResource AccentBtn}"
|
Style="{StaticResource AccentBtn}"
|
||||||
Background="{StaticResource BrGreen}"
|
Background="{StaticResource BrGreen}"
|
||||||
HorizontalAlignment="Left" Margin="0,4,0,24"
|
HorizontalAlignment="Left" Margin="0,4,0,24"
|
||||||
@@ -443,33 +529,9 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
<!-- ==== PAGE: INFO ==== -->
|
<!-- Info page removed -->
|
||||||
<Grid x:Name="pageInfo" Visibility="Collapsed" Margin="24,16">
|
|
||||||
<Border Background="#282A3A" CornerRadius="12" Padding="32"
|
|
||||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
|
||||||
MaxWidth="520">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Text="Betting Predictor" FontSize="26"
|
|
||||||
FontFamily="Segoe UI Black"
|
|
||||||
Foreground="{StaticResource BrBlue}"/>
|
|
||||||
<TextBlock Text="Versione 2.0.0" FontSize="14"
|
|
||||||
Foreground="{StaticResource BrSubtext0}" Margin="0,4,0,16"/>
|
|
||||||
<TextBlock TextWrapping="Wrap" FontSize="13" Foreground="{StaticResource BrText}"
|
|
||||||
LineHeight="22">
|
|
||||||
Applicazione per lo scaricamento e l'analisi di dati sportivi
|
|
||||||
tramite API esterne. Supporta calcio e corse dei cavalli
|
|
||||||
con esportazione in CSV.
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Text=".NET Framework 4.8.1 - WPF"
|
|
||||||
FontSize="12" Foreground="{StaticResource BrSubtext0}" Margin="0,16,0,0"/>
|
|
||||||
<TextBlock FontSize="12" Foreground="{StaticResource BrOverlay0}" Margin="0,8,0,0">
|
|
||||||
<Run Text="© 2025 - Tutti i diritti riservati"/>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
@@ -23,6 +26,139 @@ namespace HorseRacingPredictor
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_footballManager = new Football.Main();
|
_footballManager = new Football.Main();
|
||||||
_racingManager = new HorseRacing.Main(DefaultRacingUser, DefaultRacingPass);
|
_racingManager = new HorseRacing.Main(DefaultRacingUser, DefaultRacingPass);
|
||||||
|
// Wire preview update events
|
||||||
|
txtFbPrefix.TextChanged += (s, e) => UpdateFbPreview();
|
||||||
|
txtFbSuffix.TextChanged += (s, e) => UpdateFbPreview();
|
||||||
|
chkFbIncludeDate.Checked += (s, e) => UpdateFbPreview();
|
||||||
|
chkFbIncludeDate.Unchecked += (s, e) => UpdateFbPreview();
|
||||||
|
cmbFbDateFormat.SelectionChanged += (s, e) => UpdateFbPreview();
|
||||||
|
cmbFbFormat.SelectionChanged += (s, e) => UpdateFbPreview();
|
||||||
|
dpFootball.SelectedDateChanged += (s, e) => UpdateFbPreview();
|
||||||
|
|
||||||
|
txtRcPrefix.TextChanged += (s, e) => UpdateRcPreview();
|
||||||
|
txtRcSuffix.TextChanged += (s, e) => UpdateRcPreview();
|
||||||
|
chkRcIncludeDate.Checked += (s, e) => UpdateRcPreview();
|
||||||
|
chkRcIncludeDate.Unchecked += (s, e) => UpdateRcPreview();
|
||||||
|
cmbRcDateFormat.SelectionChanged += (s, e) => UpdateRcPreview();
|
||||||
|
cmbRcFormat.SelectionChanged += (s, e) => UpdateRcPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dgRacing_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
|
||||||
|
{
|
||||||
|
// Hide the "Inizio" column in the racing grid if it appears
|
||||||
|
if (e.PropertyName.Equals("Inizio", StringComparison.OrdinalIgnoreCase) || e.Column.Header?.ToString() == "Inizio")
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the row number 'No' column is visible and placed first
|
||||||
|
if (e.PropertyName.Equals("No", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// make header readable
|
||||||
|
e.Column.Header = "No";
|
||||||
|
// set width small
|
||||||
|
e.Column.Width = 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportToJson(DataTable data, string folder, string defaultName, Action<string> setStatus)
|
||||||
|
{
|
||||||
|
if (data == null || data.Rows.Count == 0) { MessageBox.Show("Nessun dato da esportare.", "Nessun dato", MessageBoxButton.OK, MessageBoxImage.Warning); return; }
|
||||||
|
// ensure file name has .json
|
||||||
|
defaultName = EnsureFileExtension(string.IsNullOrWhiteSpace(defaultName) ? "export.json" : defaultName, ".json");
|
||||||
|
string filePath;
|
||||||
|
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder)) filePath = Path.Combine(folder, defaultName);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var dlg = new Microsoft.Win32.SaveFileDialog { Filter = "File JSON|*.json", FileName = defaultName, AddExtension = true };
|
||||||
|
if (dlg.ShowDialog() != true) return;
|
||||||
|
filePath = dlg.FileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object>>();
|
||||||
|
foreach (DataRow r in data.Rows)
|
||||||
|
{
|
||||||
|
var dict = new System.Collections.Generic.Dictionary<string, object>();
|
||||||
|
foreach (DataColumn c in data.Columns)
|
||||||
|
dict[c.ColumnName] = r[c] == DBNull.Value ? null : r[c];
|
||||||
|
list.Add(dict);
|
||||||
|
}
|
||||||
|
var json = System.Text.Json.JsonSerializer.Serialize(list, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
|
||||||
|
File.WriteAllText(filePath, json, Encoding.UTF8);
|
||||||
|
setStatus?.Invoke($"JSON esportato: {Path.GetFileName(filePath)}");
|
||||||
|
MessageBox.Show($"Esportate {data.Rows.Count} righe in:\n{filePath}", "Esportazione completata", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Errore durante l'esportazione JSON:\n{ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// display count somewhere: update status label if provided
|
||||||
|
setStatus?.Invoke($"Esportate {data.Rows.Count} righe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportToXml(DataTable data, string folder, string defaultName, Action<string> setStatus)
|
||||||
|
{
|
||||||
|
if (data == null || data.Rows.Count == 0) { MessageBox.Show("Nessun dato da esportare.", "Nessun dato", MessageBoxButton.OK, MessageBoxImage.Warning); return; }
|
||||||
|
// ensure file name has .xml
|
||||||
|
defaultName = EnsureFileExtension(string.IsNullOrWhiteSpace(defaultName) ? "export.xml" : defaultName, ".xml");
|
||||||
|
string filePath;
|
||||||
|
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder)) filePath = Path.Combine(folder, defaultName);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var dlg = new Microsoft.Win32.SaveFileDialog { Filter = "File XML|*.xml", FileName = defaultName, AddExtension = true };
|
||||||
|
if (dlg.ShowDialog() != true) return;
|
||||||
|
filePath = dlg.FileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Build a simple XML without schema to avoid assembly/type resolution issues
|
||||||
|
var doc = new System.Xml.Linq.XDocument();
|
||||||
|
var root = new System.Xml.Linq.XElement("Rows");
|
||||||
|
foreach (DataRow r in data.Rows)
|
||||||
|
{
|
||||||
|
var rowEl = new System.Xml.Linq.XElement("Row");
|
||||||
|
foreach (DataColumn c in data.Columns)
|
||||||
|
{
|
||||||
|
var val = r[c] == DBNull.Value ? string.Empty : r[c].ToString();
|
||||||
|
rowEl.Add(new System.Xml.Linq.XElement(XmlConvertName(c.ColumnName), val));
|
||||||
|
}
|
||||||
|
root.Add(rowEl);
|
||||||
|
}
|
||||||
|
doc.Add(root);
|
||||||
|
doc.Save(filePath);
|
||||||
|
|
||||||
|
setStatus?.Invoke($"XML esportato: {Path.GetFileName(filePath)}");
|
||||||
|
MessageBox.Show($"Esportate {data.Rows.Count} righe in:\n{filePath}", "Esportazione completata", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Errore durante l'esportazione XML:\n{ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
setStatus?.Invoke($"Esportate {data.Rows.Count} righe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string XmlConvertName(string name)
|
||||||
|
{
|
||||||
|
// Ensure XML element name is valid: replace spaces and illegal chars with underscore
|
||||||
|
if (string.IsNullOrEmpty(name)) return "Column";
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var ch in name)
|
||||||
|
{
|
||||||
|
if (char.IsLetterOrDigit(ch) || ch == '_' || ch == '-') sb.Append(ch); else sb.Append('_');
|
||||||
|
}
|
||||||
|
// cannot start with digit
|
||||||
|
if (char.IsDigit(sb[0])) return "C_" + sb.ToString();
|
||||||
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ???????????????????? LIFECYCLE ????????????????????
|
// ???????????????????? LIFECYCLE ????????????????????
|
||||||
@@ -44,7 +180,6 @@ namespace HorseRacingPredictor
|
|||||||
if (pageFootball != null) pageFootball.Visibility = name == "football" ? Visibility.Visible : Visibility.Collapsed;
|
if (pageFootball != null) pageFootball.Visibility = name == "football" ? Visibility.Visible : Visibility.Collapsed;
|
||||||
if (pageRacing != null) pageRacing.Visibility = name == "racing" ? Visibility.Visible : Visibility.Collapsed;
|
if (pageRacing != null) pageRacing.Visibility = name == "racing" ? Visibility.Visible : Visibility.Collapsed;
|
||||||
if (pageSettings != null) pageSettings.Visibility = name == "settings" ? Visibility.Visible : Visibility.Collapsed;
|
if (pageSettings != null) pageSettings.Visibility = name == "settings" ? Visibility.Visible : Visibility.Collapsed;
|
||||||
if (pageInfo != null) pageInfo.Visibility = name == "info" ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
|
|
||||||
// Update title if available
|
// Update title if available
|
||||||
if (lblTitle != null)
|
if (lblTitle != null)
|
||||||
@@ -54,7 +189,6 @@ namespace HorseRacingPredictor
|
|||||||
case "football": lblTitle.Text = "Calcio"; break;
|
case "football": lblTitle.Text = "Calcio"; break;
|
||||||
case "racing": lblTitle.Text = "Corse Cavalli"; break;
|
case "racing": lblTitle.Text = "Corse Cavalli"; break;
|
||||||
case "settings": lblTitle.Text = "Impostazioni"; break;
|
case "settings": lblTitle.Text = "Impostazioni"; break;
|
||||||
case "info": lblTitle.Text = "Informazioni"; break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +196,6 @@ namespace HorseRacingPredictor
|
|||||||
private void navFootball_Checked(object sender, RoutedEventArgs e) => ShowPage("football");
|
private void navFootball_Checked(object sender, RoutedEventArgs e) => ShowPage("football");
|
||||||
private void navRacing_Checked(object sender, RoutedEventArgs e) => ShowPage("racing");
|
private void navRacing_Checked(object sender, RoutedEventArgs e) => ShowPage("racing");
|
||||||
private void navSettings_Checked(object sender, RoutedEventArgs e) => ShowPage("settings");
|
private void navSettings_Checked(object sender, RoutedEventArgs e) => ShowPage("settings");
|
||||||
private void navInfo_Checked(object sender, RoutedEventArgs e) => ShowPage("info");
|
|
||||||
|
|
||||||
// ???????????????????? FOOTBALL ????????????????????
|
// ???????????????????? FOOTBALL ????????????????????
|
||||||
|
|
||||||
@@ -89,6 +222,10 @@ namespace HorseRacingPredictor
|
|||||||
_footballManager.GetTodayFixtures(date, progress, status));
|
_footballManager.GetTodayFixtures(date, progress, status));
|
||||||
|
|
||||||
_footballData = table;
|
_footballData = table;
|
||||||
|
|
||||||
|
// Ensure the start time column exists and populate it (no timezone label)
|
||||||
|
InjectRomeStartTimeColumn(_footballData, "Inizio");
|
||||||
|
|
||||||
dgFootball.ItemsSource = _footballData?.DefaultView;
|
dgFootball.ItemsSource = _footballData?.DefaultView;
|
||||||
|
|
||||||
if (_footballData != null && _footballData.Rows.Count > 0)
|
if (_footballData != null && _footballData.Rows.Count > 0)
|
||||||
@@ -117,18 +254,224 @@ namespace HorseRacingPredictor
|
|||||||
|
|
||||||
private void btnExportFbCsv_Click(object sender, RoutedEventArgs e)
|
private void btnExportFbCsv_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ExportToCsv(_footballData, txtFbExportPath.Text,
|
var format = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||||
$"Partite_{dpFootball.SelectedDate:yyyy-MM-dd}.csv",
|
var filename = BuildFilename(txtFbPrefix?.Text, chkFbIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbFbDateFormat, dpFootball.SelectedDate ?? DateTime.Today) : null, txtFbSuffix?.Text, null, $"Partite_{dpFootball.SelectedDate:yyyy-MM-dd}.{format.ToLower()}");
|
||||||
s => lblStatusFb.Text = s);
|
filename = EnsureFileExtension(SanitizeFileName(filename), "." + format.ToLower());
|
||||||
|
|
||||||
|
switch (format.ToUpper())
|
||||||
|
{
|
||||||
|
case "CSV":
|
||||||
|
ExportToCsv(_footballData, txtFbExportPath.Text, filename, s => lblStatusFb.Text = s);
|
||||||
|
break;
|
||||||
|
case "JSON":
|
||||||
|
ExportToJson(_footballData, txtFbExportPath.Text, filename, s => lblStatusFb.Text = s);
|
||||||
|
break;
|
||||||
|
case "XML":
|
||||||
|
ExportToXml(_footballData, txtFbExportPath.Text, filename, s => lblStatusFb.Text = s);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ExportToCsv(_footballData, txtFbExportPath.Text, filename, s => lblStatusFb.Text = s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// show total rows extracted
|
||||||
|
lblStatusFb.Text = _footballData == null ? "Nessuna riga" : $"Righe estratte: {_footballData.Rows.Count}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ???????????????????? HORSE RACING ????????????????????
|
// ???????????????????? HORSE RACING ????????????????????
|
||||||
|
|
||||||
|
private void rbRcSource_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Toggle visibility of API vs CSV controls
|
||||||
|
if (cmbDay == null || btnDownloadRc == null || btnBrowseCsvRc == null) return;
|
||||||
|
bool isApi = rbRcApi.IsChecked == true;
|
||||||
|
cmbDay.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
btnDownloadRc.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
btnBrowseCsvRc.Visibility = isApi ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
private async void btnDownloadRc_Click(object sender, RoutedEventArgs e)
|
private async void btnDownloadRc_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
await DownloadRacecardsAsync();
|
await DownloadRacecardsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void btnBrowseCsvRc_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
|
||||||
|
{
|
||||||
|
dlg.Description = "Seleziona la cartella con i file CSV Punters";
|
||||||
|
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lblStatusRc.Text = "Caricamento file CSV…";
|
||||||
|
pbRacing.Value = 0;
|
||||||
|
btnExportRcCsv.IsEnabled = false;
|
||||||
|
|
||||||
|
var csvFiles = Directory.GetFiles(dlg.SelectedPath, "*.csv", SearchOption.AllDirectories)
|
||||||
|
.OrderBy(f => f)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (csvFiles.Count == 0)
|
||||||
|
{
|
||||||
|
lblStatusRc.Text = "Nessun file CSV trovato nella cartella selezionata";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all CSV files into a single DataTable preserving all original columns
|
||||||
|
var table = new DataTable();
|
||||||
|
table.Columns.Add("Meeting", typeof(string));
|
||||||
|
table.Columns.Add("Race", typeof(int));
|
||||||
|
|
||||||
|
int processed = 0;
|
||||||
|
foreach (var file in csvFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Extract meeting name and race number from filename pattern YYYYMMDD-meeting-rXX.csv
|
||||||
|
string fileName = Path.GetFileNameWithoutExtension(file);
|
||||||
|
string meetingName = fileName;
|
||||||
|
int raceNumber = 0;
|
||||||
|
var m = Regex.Match(Path.GetFileName(file), @"^\d{8}-(.+)-r(\d+)\.csv$", RegexOptions.IgnoreCase);
|
||||||
|
if (m.Success)
|
||||||
|
{
|
||||||
|
meetingName = string.Join(" ", m.Groups[1].Value.Split('-')
|
||||||
|
.Select(s => s.Length > 0 ? char.ToUpper(s[0]) + s.Substring(1).ToLower() : s));
|
||||||
|
int.TryParse(m.Groups[2].Value, out raceNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read CSV with simple parser (comma-delimited, first row = header)
|
||||||
|
var lines = File.ReadAllLines(file, Encoding.UTF8);
|
||||||
|
if (lines.Length < 2) { processed++; continue; }
|
||||||
|
|
||||||
|
var headers = ParseCsvLine(lines[0]);
|
||||||
|
|
||||||
|
// Ensure all columns exist in the merged DataTable
|
||||||
|
foreach (var h in headers)
|
||||||
|
{
|
||||||
|
string colName = h.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(colName)) continue;
|
||||||
|
if (!table.Columns.Contains(colName))
|
||||||
|
table.Columns.Add(colName, typeof(string));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse data rows
|
||||||
|
for (int i = 1; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(lines[i])) continue;
|
||||||
|
var values = ParseCsvLine(lines[i]);
|
||||||
|
var row = table.NewRow();
|
||||||
|
row["Meeting"] = meetingName;
|
||||||
|
row["Race"] = raceNumber;
|
||||||
|
for (int c = 0; c < headers.Length && c < values.Length; c++)
|
||||||
|
{
|
||||||
|
string colName = headers[c].Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(colName)) continue;
|
||||||
|
if (table.Columns.Contains(colName))
|
||||||
|
row[colName] = values[c]?.Trim() ?? "";
|
||||||
|
}
|
||||||
|
table.Rows.Add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Errore CSV {file}: {ex.Message}");
|
||||||
|
}
|
||||||
|
processed++;
|
||||||
|
pbRacing.Value = (int)((double)processed / csvFiles.Count * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add row numbers
|
||||||
|
InjectRowNumbers(table);
|
||||||
|
|
||||||
|
_racingData = table;
|
||||||
|
dgRacing.ItemsSource = _racingData?.DefaultView;
|
||||||
|
|
||||||
|
if (_racingData.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
btnExportRcCsv.IsEnabled = true;
|
||||||
|
lblStatusRc.Text = $"Caricati {_racingData.Rows.Count} cavalli da {csvFiles.Count} file CSV";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lblStatusRc.Text = "Nessun cavallo trovato nei file CSV";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Errore durante il caricamento CSV:\n{ex.Message}",
|
||||||
|
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
lblStatusRc.Text = "Errore nel caricamento CSV";
|
||||||
|
pbRacing.Value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a single CSV line respecting quoted fields (comma delimiter).
|
||||||
|
/// </summary>
|
||||||
|
private static string[] ParseCsvLine(string line)
|
||||||
|
{
|
||||||
|
var fields = new List<string>();
|
||||||
|
bool inQuotes = false;
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < line.Length; i++)
|
||||||
|
{
|
||||||
|
char c = line[i];
|
||||||
|
if (inQuotes)
|
||||||
|
{
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
if (i + 1 < line.Length && line[i + 1] == '"')
|
||||||
|
{
|
||||||
|
sb.Append('"');
|
||||||
|
i++; // skip escaped quote
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inQuotes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
inQuotes = true;
|
||||||
|
}
|
||||||
|
else if (c == ',')
|
||||||
|
{
|
||||||
|
fields.Add(sb.ToString());
|
||||||
|
sb.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields.Add(sb.ToString());
|
||||||
|
return fields.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a "No" row-number column as the first column in the DataTable.
|
||||||
|
/// </summary>
|
||||||
|
private static void InjectRowNumbers(DataTable table)
|
||||||
|
{
|
||||||
|
if (table == null || table.Rows.Count == 0) return;
|
||||||
|
if (table.Columns.Contains("No")) table.Columns.Remove("No");
|
||||||
|
var col = new DataColumn("No", typeof(int));
|
||||||
|
table.Columns.Add(col);
|
||||||
|
col.SetOrdinal(0);
|
||||||
|
int n = 1;
|
||||||
|
foreach (DataRow r in table.Rows)
|
||||||
|
r[col] = n++;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DownloadRacecardsAsync()
|
private async Task DownloadRacecardsAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -148,6 +491,10 @@ namespace HorseRacingPredictor
|
|||||||
_racingManager.GetRacecards(day, progress, status));
|
_racingManager.GetRacecards(day, progress, status));
|
||||||
|
|
||||||
_racingData = table;
|
_racingData = table;
|
||||||
|
|
||||||
|
// Add only row numbers for racing (do not add an "Inizio" column — meeting name already contains time)
|
||||||
|
InjectRomeStartTimeColumn(_racingData, null);
|
||||||
|
|
||||||
dgRacing.ItemsSource = _racingData?.DefaultView;
|
dgRacing.ItemsSource = _racingData?.DefaultView;
|
||||||
|
|
||||||
if (_racingData != null && _racingData.Rows.Count > 0)
|
if (_racingData != null && _racingData.Rows.Count > 0)
|
||||||
@@ -177,9 +524,27 @@ namespace HorseRacingPredictor
|
|||||||
private void btnExportRcCsv_Click(object sender, RoutedEventArgs e)
|
private void btnExportRcCsv_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
string dayLabel = cmbDay.SelectedIndex == 0 ? "oggi" : "domani";
|
string dayLabel = cmbDay.SelectedIndex == 0 ? "oggi" : "domani";
|
||||||
ExportToCsv(_racingData, txtRcExportPath.Text,
|
var format = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||||
$"Corse_{dayLabel}_{DateTime.Now:yyyy-MM-dd}.csv",
|
var defaultName = $"Corse_{dayLabel}_{DateTime.Now:yyyy-MM-dd}.{format.ToLower()}";
|
||||||
s => lblStatusRc.Text = s);
|
var filename = BuildFilename(txtRcPrefix?.Text, chkRcIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbRcDateFormat, DateTime.Now) : null, txtRcSuffix?.Text, null, defaultName);
|
||||||
|
filename = EnsureFileExtension(SanitizeFileName(filename), "." + format.ToLower());
|
||||||
|
|
||||||
|
switch (format.ToUpper())
|
||||||
|
{
|
||||||
|
case "CSV":
|
||||||
|
ExportToCsv(_racingData, txtRcExportPath.Text, filename, s => lblStatusRc.Text = s);
|
||||||
|
break;
|
||||||
|
case "JSON":
|
||||||
|
ExportToJson(_racingData, txtRcExportPath.Text, filename, s => lblStatusRc.Text = s);
|
||||||
|
break;
|
||||||
|
case "XML":
|
||||||
|
ExportToXml(_racingData, txtRcExportPath.Text, filename, s => lblStatusRc.Text = s);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ExportToCsv(_racingData, txtRcExportPath.Text, filename, s => lblStatusRc.Text = s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lblStatusRc.Text = _racingData == null ? "Nessuna riga" : $"Righe estratte: {_racingData.Rows.Count}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ???????????????????? SHARED CSV EXPORT ????????????????????
|
// ???????????????????? SHARED CSV EXPORT ????????????????????
|
||||||
@@ -193,6 +558,8 @@ namespace HorseRacingPredictor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure filename has .csv extension
|
||||||
|
defaultName = EnsureFileExtension(defaultName, ".csv");
|
||||||
string filePath;
|
string filePath;
|
||||||
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder))
|
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder))
|
||||||
{
|
{
|
||||||
@@ -203,7 +570,8 @@ namespace HorseRacingPredictor
|
|||||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||||
{
|
{
|
||||||
Filter = "File CSV|*.csv",
|
Filter = "File CSV|*.csv",
|
||||||
FileName = defaultName
|
FileName = defaultName,
|
||||||
|
AddExtension = true
|
||||||
};
|
};
|
||||||
if (dlg.ShowDialog() != true) return;
|
if (dlg.ShowDialog() != true) return;
|
||||||
filePath = dlg.FileName;
|
filePath = dlg.FileName;
|
||||||
@@ -240,6 +608,10 @@ namespace HorseRacingPredictor
|
|||||||
MessageBox.Show($"Errore durante l'esportazione CSV:\n{ex.Message}",
|
MessageBox.Show($"Errore durante l'esportazione CSV:\n{ex.Message}",
|
||||||
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
setStatus?.Invoke($"Esportate {data.Rows.Count} righe");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ???????????????????? FOLDER BROWSE ????????????????????
|
// ???????????????????? FOLDER BROWSE ????????????????????
|
||||||
@@ -248,12 +620,14 @@ namespace HorseRacingPredictor
|
|||||||
{
|
{
|
||||||
var path = BrowseFolder("Seleziona la cartella di esportazione per Calcio");
|
var path = BrowseFolder("Seleziona la cartella di esportazione per Calcio");
|
||||||
if (path != null) txtFbExportPath.Text = path;
|
if (path != null) txtFbExportPath.Text = path;
|
||||||
|
UpdateFbPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnBrowseRcExport_Click(object sender, RoutedEventArgs e)
|
private void btnBrowseRcExport_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var path = BrowseFolder("Seleziona la cartella di esportazione per Corse Cavalli");
|
var path = BrowseFolder("Seleziona la cartella di esportazione per Corse Cavalli");
|
||||||
if (path != null) txtRcExportPath.Text = path;
|
if (path != null) txtRcExportPath.Text = path;
|
||||||
|
UpdateRcPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BrowseFolder(string description)
|
private static string BrowseFolder(string description)
|
||||||
@@ -289,16 +663,251 @@ namespace HorseRacingPredictor
|
|||||||
|
|
||||||
if (key == "ApiKey") txtApiKey.Text = val;
|
if (key == "ApiKey") txtApiKey.Text = val;
|
||||||
else if (key == "FbExportPath") txtFbExportPath.Text = val;
|
else if (key == "FbExportPath") txtFbExportPath.Text = val;
|
||||||
|
else if (key == "FbPrefix") txtFbPrefix.Text = val;
|
||||||
|
else if (key == "FbSuffix") txtFbSuffix.Text = val;
|
||||||
|
else if (key == "FbIncludeDate") chkFbIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||||
|
else if (key == "FbDateFormat") { try { SetComboBoxSelectionByContent(cmbFbDateFormat, val); } catch { } }
|
||||||
|
else if (key == "FbFormat") { try { SetComboBoxSelectionByContent(cmbFbFormat, val); } catch { } }
|
||||||
else if (key == "RcExportPath") txtRcExportPath.Text = val;
|
else if (key == "RcExportPath") txtRcExportPath.Text = val;
|
||||||
|
else if (key == "RcPrefix") txtRcPrefix.Text = val;
|
||||||
|
else if (key == "RcSuffix") txtRcSuffix.Text = val;
|
||||||
|
else if (key == "RcIncludeDate") chkRcIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||||
|
else if (key == "RcDateFormat") { try { SetComboBoxSelectionByContent(cmbRcDateFormat, val); } catch { } }
|
||||||
|
else if (key == "RcFormat") { try { SetComboBoxSelectionByContent(cmbRcFormat, val); } catch { } }
|
||||||
else if (key == "RacingUser") txtRacingUser.Text = val;
|
else if (key == "RacingUser") txtRacingUser.Text = val;
|
||||||
else if (key == "RacingPass") txtRacingPass.Password = val;
|
else if (key == "RacingPass") txtRacingPass.Password = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update preview UI after loading values
|
||||||
|
UpdateFbPreview();
|
||||||
|
UpdateRcPreview();
|
||||||
|
|
||||||
_racingManager.UpdateCredentials(txtRacingUser.Text, txtRacingPass.Password);
|
_racingManager.UpdateCredentials(txtRacingUser.Text, txtRacingPass.Password);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetComboBoxSelectionByContent(ComboBox combo, string content)
|
||||||
|
{
|
||||||
|
if (combo == null) return;
|
||||||
|
for (int i = 0; i < combo.Items.Count; i++)
|
||||||
|
{
|
||||||
|
var item = combo.Items[i] as ComboBoxItem;
|
||||||
|
if (item != null && string.Equals(item.Content?.ToString(), content, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
combo.SelectedIndex = i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EnsureFileExtension(string fileName, string extension)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName)) return "";
|
||||||
|
if (!extension.StartsWith(".")) extension = "." + extension;
|
||||||
|
if (fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) return fileName;
|
||||||
|
return fileName + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildFilename(string prefix, string datePart, string suffix, string explicitName, string fallback)
|
||||||
|
{
|
||||||
|
// If user provided explicit full filename, use it
|
||||||
|
if (!string.IsNullOrWhiteSpace(explicitName)) return SanitizeFileName(explicitName.Trim());
|
||||||
|
|
||||||
|
prefix = prefix ?? "";
|
||||||
|
suffix = suffix ?? "";
|
||||||
|
// The user may include underscores in prefix/suffix as desired
|
||||||
|
|
||||||
|
string middle = string.IsNullOrWhiteSpace(datePart) ? "" : datePart;
|
||||||
|
|
||||||
|
string name = (prefix ?? string.Empty) + middle + (suffix ?? string.Empty);
|
||||||
|
if (string.IsNullOrWhiteSpace(name)) return fallback;
|
||||||
|
return SanitizeFileName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSelectedDateString(ComboBox combo, DateTime? date)
|
||||||
|
{
|
||||||
|
if (date == null) return null;
|
||||||
|
var fmt = (combo?.SelectedItem as ComboBoxItem)?.Content?.ToString();
|
||||||
|
if (string.IsNullOrWhiteSpace(fmt)) fmt = "yyyy-MM-dd";
|
||||||
|
try { return date.Value.ToString(fmt); } catch { return date.Value.ToString("yyyy-MM-dd"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SanitizeFileName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name)) return name;
|
||||||
|
var invalid = Path.GetInvalidFileNameChars();
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var ch in name)
|
||||||
|
{
|
||||||
|
if (Array.IndexOf(invalid, ch) >= 0)
|
||||||
|
continue; // remove invalid characters
|
||||||
|
sb.Append(ch);
|
||||||
|
}
|
||||||
|
// trim whitespace
|
||||||
|
return sb.ToString().Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFbPreview()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var format = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||||
|
var datePart = chkFbIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbFbDateFormat, dpFootball.SelectedDate ?? DateTime.Today) : null;
|
||||||
|
var name = BuildFilename(txtFbPrefix?.Text, datePart, txtFbSuffix?.Text, null, $"Partite_{(dpFootball.SelectedDate ?? DateTime.Today):yyyy-MM-dd}.{format.ToLower()}");
|
||||||
|
name = SanitizeFileName(name);
|
||||||
|
name = EnsureFileExtension(name, "." + format.ToLower());
|
||||||
|
if (txtFbPreview != null) txtFbPreview.Text = name;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRcPreview()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var format = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||||
|
var datePart = chkRcIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbRcDateFormat, DateTime.Now) : null;
|
||||||
|
var defaultName = $"Corse_{(cmbDay.SelectedIndex==0?"oggi":"domani")}_{DateTime.Now:yyyy-MM-dd}.{format.ToLower()}";
|
||||||
|
var name = BuildFilename(txtRcPrefix?.Text, datePart, txtRcSuffix?.Text, null, defaultName);
|
||||||
|
name = SanitizeFileName(name);
|
||||||
|
name = EnsureFileExtension(name, "." + format.ToLower());
|
||||||
|
if (txtRcPreview != null) txtRcPreview.Text = name;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure a visible column with the fixture/race start time converted to Rome timezone exists in the DataTable.
|
||||||
|
/// It attempts to use a unix timestamp column (unix_ts) or a date column (Data / Ora / date) and adds a formatted column.
|
||||||
|
/// </summary>
|
||||||
|
private void InjectRomeStartTimeColumn(DataTable table, string columnName)
|
||||||
|
{
|
||||||
|
if (table == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// If no columnName specified, only add/populate row number column and return
|
||||||
|
if (string.IsNullOrWhiteSpace(columnName))
|
||||||
|
{
|
||||||
|
if (table.Columns.Contains("No")) table.Columns.Remove("No");
|
||||||
|
var rowOnly = new DataColumn("No", typeof(int));
|
||||||
|
table.Columns.Add(rowOnly);
|
||||||
|
rowOnly.SetOrdinal(0);
|
||||||
|
int rnOnly = 1;
|
||||||
|
foreach (DataRow r in table.Rows)
|
||||||
|
{
|
||||||
|
try { r[rowOnly] = rnOnly++; } catch { }
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any existing columns with the same names to avoid duplicates
|
||||||
|
if (table.Columns.Contains(columnName)) table.Columns.Remove(columnName);
|
||||||
|
if (table.Columns.Contains("No")) table.Columns.Remove("No");
|
||||||
|
|
||||||
|
// Add row number column as first column
|
||||||
|
var rowNoCol = new DataColumn("No", typeof(int));
|
||||||
|
table.Columns.Add(rowNoCol);
|
||||||
|
rowNoCol.SetOrdinal(0);
|
||||||
|
|
||||||
|
// Add new column as string for formatted display and place it after 'No'
|
||||||
|
var col = new DataColumn(columnName, typeof(string));
|
||||||
|
table.Columns.Add(col);
|
||||||
|
col.SetOrdinal(1);
|
||||||
|
|
||||||
|
// Populate row numbers immediately
|
||||||
|
int rn = 1;
|
||||||
|
foreach (DataRow r in table.Rows)
|
||||||
|
{
|
||||||
|
try { r[rowNoCol] = rn++; } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine timezone for Rome (Windows TZ id). Fallback to UTC+1 offset if not found.
|
||||||
|
TimeZoneInfo romeTz = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
romeTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
try { romeTz = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time"); } catch { romeTz = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to convert DateTimeOffset to Rome local time and format
|
||||||
|
Func<DateTimeOffset, string> fmt = dto =>
|
||||||
|
{
|
||||||
|
DateTimeOffset rome;
|
||||||
|
if (romeTz != null)
|
||||||
|
rome = TimeZoneInfo.ConvertTime(dto, romeTz);
|
||||||
|
else
|
||||||
|
rome = dto.ToOffset(TimeSpan.FromHours(1)); // fallback UTC+1
|
||||||
|
|
||||||
|
return rome.ToString("yyyy-MM-dd HH:mm");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try unix timestamp first
|
||||||
|
if (table.Columns.Contains("unix_ts"))
|
||||||
|
{
|
||||||
|
foreach (DataRow r in table.Rows)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var obj = r["unix_ts"];
|
||||||
|
if (obj == DBNull.Value) { r[col] = string.Empty; continue; }
|
||||||
|
long ts = 0;
|
||||||
|
if (obj is long) ts = (long)obj;
|
||||||
|
else if (obj is int) ts = Convert.ToInt64(obj);
|
||||||
|
else ts = Convert.ToInt64(obj);
|
||||||
|
|
||||||
|
var dto = DateTimeOffset.FromUnixTimeSeconds(ts);
|
||||||
|
r[col] = fmt(dto);
|
||||||
|
}
|
||||||
|
catch { r[col] = string.Empty; }
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise try common date columns
|
||||||
|
string[] candidates = new[] { "Data / Ora", "date", "Date", "start", "kickoff" };
|
||||||
|
foreach (var c in candidates)
|
||||||
|
{
|
||||||
|
if (!table.Columns.Contains(c)) continue;
|
||||||
|
foreach (DataRow r in table.Rows)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var v = r[c];
|
||||||
|
if (v == DBNull.Value) { r[col] = string.Empty; continue; }
|
||||||
|
|
||||||
|
DateTimeOffset dto;
|
||||||
|
if (v is DateTime dt)
|
||||||
|
{
|
||||||
|
// treat as UTC if unspecified
|
||||||
|
dto = new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// parse string including offset
|
||||||
|
dto = DateTimeOffset.Parse(v.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
r[col] = fmt(dto);
|
||||||
|
}
|
||||||
|
catch { r[col] = string.Empty; }
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no source found, leave column empty
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Non-fatal: log to debug output
|
||||||
|
System.Diagnostics.Debug.WriteLine("InjectRomeStartTimeColumn error: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void btnSaveSettings_Click(object sender, RoutedEventArgs e)
|
private void btnSaveSettings_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -306,11 +915,25 @@ namespace HorseRacingPredictor
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine($"ApiKey={txtApiKey.Text.Trim()}");
|
sb.AppendLine($"ApiKey={txtApiKey.Text.Trim()}");
|
||||||
sb.AppendLine($"FbExportPath={txtFbExportPath.Text.Trim()}");
|
sb.AppendLine($"FbExportPath={txtFbExportPath.Text.Trim()}");
|
||||||
|
sb.AppendLine($"FbPrefix={txtFbPrefix.Text.Trim()}");
|
||||||
|
sb.AppendLine($"FbSuffix={txtFbSuffix.Text.Trim()}");
|
||||||
|
sb.AppendLine($"FbIncludeDate={(chkFbIncludeDate.IsChecked==true?"1":"0")}");
|
||||||
|
sb.AppendLine($"FbDateFormat={(cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
|
||||||
|
sb.AppendLine($"FbFormat={(cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
|
||||||
sb.AppendLine($"RcExportPath={txtRcExportPath.Text.Trim()}");
|
sb.AppendLine($"RcExportPath={txtRcExportPath.Text.Trim()}");
|
||||||
|
sb.AppendLine($"RcPrefix={txtRcPrefix.Text.Trim()}");
|
||||||
|
sb.AppendLine($"RcSuffix={txtRcSuffix.Text.Trim()}");
|
||||||
|
sb.AppendLine($"RcIncludeDate={(chkRcIncludeDate.IsChecked==true?"1":"0")}");
|
||||||
|
sb.AppendLine($"RcDateFormat={(cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
|
||||||
|
sb.AppendLine($"RcFormat={(cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
|
||||||
sb.AppendLine($"RacingUser={txtRacingUser.Text.Trim()}");
|
sb.AppendLine($"RacingUser={txtRacingUser.Text.Trim()}");
|
||||||
sb.AppendLine($"RacingPass={txtRacingPass.Password.Trim()}");
|
sb.AppendLine($"RacingPass={txtRacingPass.Password.Trim()}");
|
||||||
File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8);
|
File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8);
|
||||||
|
|
||||||
|
// update previews after save
|
||||||
|
UpdateFbPreview();
|
||||||
|
UpdateRcPreview();
|
||||||
|
|
||||||
_racingManager.UpdateCredentials(txtRacingUser.Text.Trim(), txtRacingPass.Password.Trim());
|
_racingManager.UpdateCredentials(txtRacingUser.Text.Trim(), txtRacingPass.Password.Trim());
|
||||||
|
|
||||||
MessageBox.Show("Impostazioni salvate con successo.",
|
MessageBox.Show("Impostazioni salvate con successo.",
|
||||||
|
|||||||
249
HorseRacingPredictor/Styles/GlobalStyles.xaml
Normal file
249
HorseRacingPredictor/Styles/GlobalStyles.xaml
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<!-- =========================================================
|
||||||
|
CATPPUCCIN MOCHA PALETTE (shared)
|
||||||
|
========================================================= -->
|
||||||
|
<Color x:Key="CBase">#1E1E2E</Color>
|
||||||
|
<Color x:Key="CMantle">#181825</Color>
|
||||||
|
<Color x:Key="CCrust">#11111B</Color>
|
||||||
|
<Color x:Key="CSurface0">#313244</Color>
|
||||||
|
<Color x:Key="CSurface1">#45475A</Color>
|
||||||
|
<Color x:Key="CSurface2">#585B70</Color>
|
||||||
|
<Color x:Key="COverlay0">#6C7086</Color>
|
||||||
|
<Color x:Key="CText">#CDD6F4</Color>
|
||||||
|
<Color x:Key="CSubtext0">#A6ADC8</Color>
|
||||||
|
<Color x:Key="CSubtext1">#BAC2DE</Color>
|
||||||
|
<Color x:Key="CBlue">#89B4FA</Color>
|
||||||
|
<Color x:Key="CGreen">#A6E3A1</Color>
|
||||||
|
<Color x:Key="CRed">#F38BA8</Color>
|
||||||
|
<Color x:Key="CPeach">#FAB387</Color>
|
||||||
|
<Color x:Key="CLavender">#B4BEFE</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="BrBase" Color="{StaticResource CBase}"/>
|
||||||
|
<SolidColorBrush x:Key="BrMantle" Color="{StaticResource CMantle}"/>
|
||||||
|
<SolidColorBrush x:Key="BrSurface0" Color="{StaticResource CSurface0}"/>
|
||||||
|
<SolidColorBrush x:Key="BrSurface1" Color="{StaticResource CSurface1}"/>
|
||||||
|
<SolidColorBrush x:Key="BrSurface2" Color="{StaticResource CSurface2}"/>
|
||||||
|
<SolidColorBrush x:Key="BrOverlay0" Color="{StaticResource COverlay0}"/>
|
||||||
|
<SolidColorBrush x:Key="BrText" Color="{StaticResource CText}"/>
|
||||||
|
<SolidColorBrush x:Key="BrSubtext0" Color="{StaticResource CSubtext0}"/>
|
||||||
|
<SolidColorBrush x:Key="BrBlue" Color="{StaticResource CBlue}"/>
|
||||||
|
<SolidColorBrush x:Key="BrGreen" Color="{StaticResource CGreen}"/>
|
||||||
|
<SolidColorBrush x:Key="BrRed" Color="{StaticResource CRed}"/>
|
||||||
|
<SolidColorBrush x:Key="BrPeach" Color="{StaticResource CPeach}"/>
|
||||||
|
<SolidColorBrush x:Key="BrLavender" Color="{StaticResource CLavender}"/>
|
||||||
|
|
||||||
|
<!-- Acrylic-like background (semi-transparent fallback) -->
|
||||||
|
<SolidColorBrush x:Key="AcrylicBackgroundBrush" Color="#0F000000"/>
|
||||||
|
|
||||||
|
<!-- Subtle shadow effect for elevation -->
|
||||||
|
<DropShadowEffect x:Key="SubtleDropShadow" BlurRadius="12" ShadowDepth="2" Color="#50000000"/>
|
||||||
|
|
||||||
|
<!-- NAV BUTTON STYLE (icon-only sidebar) -->
|
||||||
|
<Style x:Key="NavBtn" TargetType="RadioButton">
|
||||||
|
<Setter Property="Width" Value="48"/>
|
||||||
|
<Setter Property="Height" Value="48"/>
|
||||||
|
<Setter Property="Margin" Value="6,4"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource BrSubtext0}"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="RadioButton">
|
||||||
|
<Grid>
|
||||||
|
<Border x:Name="Bg" CornerRadius="10" Background="{TemplateBinding Background}"/>
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Bg" Property="Background" Value="#28283A"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="Bg" Property="Background" Value="{StaticResource BrBlue}"/>
|
||||||
|
<Setter Property="Foreground" Value="#181825"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ACCENT BUTTON -->
|
||||||
|
<Style x:Key="AccentBtn" TargetType="Button">
|
||||||
|
<Setter Property="Foreground" Value="#181825"/>
|
||||||
|
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
|
||||||
|
<Setter Property="FontSize" Value="13"/>
|
||||||
|
<Setter Property="Padding" Value="18,7"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border x:Name="Bd" CornerRadius="8"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Bd" Property="Opacity" Value="0.85"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter TargetName="Bd" Property="Opacity" Value="0.40"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- FLAT TEXTBOX -->
|
||||||
|
<Style x:Key="FlatTb" TargetType="TextBox">
|
||||||
|
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||||
|
<Setter Property="CaretBrush" Value="{StaticResource BrText}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource BrSurface1}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="10,7"/>
|
||||||
|
<Setter Property="FontSize" Value="13"/>
|
||||||
|
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="TextBox">
|
||||||
|
<Border x:Name="Bd" CornerRadius="6"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ScrollViewer x:Name="PART_ContentHost"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsFocused" Value="True">
|
||||||
|
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- PASSWORD BOX -->
|
||||||
|
<Style x:Key="FlatPb" TargetType="PasswordBox">
|
||||||
|
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||||
|
<Setter Property="CaretBrush" Value="{StaticResource BrText}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource BrSurface1}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="10,7"/>
|
||||||
|
<Setter Property="FontSize" Value="13"/>
|
||||||
|
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="PasswordBox">
|
||||||
|
<Border x:Name="Bd" CornerRadius="6"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ScrollViewer x:Name="PART_ContentHost"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsFocused" Value="True">
|
||||||
|
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- PROGRESS BAR -->
|
||||||
|
<Style x:Key="ModernPb" TargetType="ProgressBar">
|
||||||
|
<Setter Property="Height" Value="4"/>
|
||||||
|
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ProgressBar">
|
||||||
|
<Grid>
|
||||||
|
<Border CornerRadius="2" Background="{TemplateBinding Background}"/>
|
||||||
|
<Border x:Name="PART_Indicator" CornerRadius="2"
|
||||||
|
Background="{TemplateBinding Foreground}"
|
||||||
|
HorizontalAlignment="Left"/>
|
||||||
|
<Border x:Name="PART_Track"/>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- DATAGRID -->
|
||||||
|
<Style x:Key="ModernDg" TargetType="DataGrid">
|
||||||
|
<Setter Property="Background" Value="#282A3A"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="RowBackground" Value="#282A3A"/>
|
||||||
|
<Setter Property="AlternatingRowBackground" Value="#2D2F42"/>
|
||||||
|
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
|
||||||
|
<Setter Property="HorizontalGridLinesBrush" Value="#37394E"/>
|
||||||
|
<Setter Property="HeadersVisibility" Value="Column"/>
|
||||||
|
<Setter Property="RowHeaderWidth" Value="0"/>
|
||||||
|
<Setter Property="AutoGenerateColumns" Value="True"/>
|
||||||
|
<Setter Property="IsReadOnly" Value="True"/>
|
||||||
|
<Setter Property="SelectionMode" Value="Single"/>
|
||||||
|
<Setter Property="CanUserAddRows" Value="False"/>
|
||||||
|
<Setter Property="CanUserDeleteRows" Value="False"/>
|
||||||
|
<Setter Property="CanUserResizeRows" Value="False"/>
|
||||||
|
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||||
|
<Setter Property="FontSize" Value="13"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="Background" Value="#23243A"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
||||||
|
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
|
||||||
|
<Setter Property="FontSize" Value="13"/>
|
||||||
|
<Setter Property="Padding" Value="10,8"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#37394E"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="DataGridCell">
|
||||||
|
<Setter Property="Padding" Value="8,6"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="DataGridCell">
|
||||||
|
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}">
|
||||||
|
<ContentPresenter VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter Property="Background" Value="#3C3F58"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="DataGridRow">
|
||||||
|
<Setter Property="Margin" Value="0"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter Property="Background" Value="#3C3F58"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Card style for lists / rows -->
|
||||||
|
<Style x:Key="CardStyle" TargetType="Border">
|
||||||
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
|
<Setter Property="Background" Value="#23232A"/>
|
||||||
|
<Setter Property="Padding" Value="12"/>
|
||||||
|
<Setter Property="Margin" Value="6"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
Reference in New Issue
Block a user