Compare commits

...

2 Commits

Author SHA1 Message Date
c8c674dd1c Aggiorna gestione JSON e aggiungi controlli UI moderni
Sostituito System.Text.Json con Newtonsoft.Json (JToken) nei database manager per maggiore compatibilità e robustezza.
Aggiunti nuovi controlli personalizzati (ModernButton, ModernDataGridView, ModernDateTimePicker, ModernLabel, ModernPanel, ModernProgressBar, ModernTabControl, ModernTextBox) per un'interfaccia moderna a tema scuro.
Aggiornate e aggiunte dipendenze NuGet (CsvHelper, Microsoft.ML, Newtonsoft.Json, RestSharp, ecc.).
Rimossa una using non utilizzata in API.cs.
2026-02-25 20:00:09 +01:00
72bd778d56 Migrazione UI a WPF e integrazione Racing API moderna
Transizione completa da WinForms a WPF con interfaccia moderna (sidebar, pagine, palette Catppuccin Mocha).
Aggiunta integrazione con The Racing API per le corse dei cavalli, inclusi nuovi moduli di backend e parsing dati.
Introdotti componenti UI personalizzati (CardPanel, ModernButton, ModernProgressBar, NavButton, ModernTheme) per un look coerente e moderno.
Gestione avanzata delle impostazioni, esportazione CSV migliorata, refactoring della logica di business e maggiore robustezza nelle chiamate API.
Aggiunti stub CsvHelper e file di progetto di backup per facilitare la compatibilità e la migrazione.
2026-02-25 18:49:48 +01:00
19 changed files with 178766 additions and 1162 deletions

View File

@@ -0,0 +1,6 @@
<Application x:Class="HorseRacingPredictor.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources />
</Application>

View File

@@ -0,0 +1,8 @@
using System.Windows;
namespace HorseRacingPredictor
{
public partial class App : Application
{
}
}

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props" Condition="Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props')" />
<Import Project="..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props" Condition="Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props')" />
<Import Project="..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props" Condition="Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props')" />
<Import Project="..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props" Condition="Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -13,6 +13,7 @@
<AssemblyName>HorseRacingPredictor</AssemblyName>
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
@@ -43,9 +44,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
@@ -53,80 +52,78 @@
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="CsvHelper, Version=33.0.0.0, Culture=neutral, PublicKeyToken=8c4959082be5c823, processorArchitecture=MSIL">
<HintPath>..\packages\CsvHelper.33.1.0\lib\net48\CsvHelper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Dapper, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.2.1.66\lib\net461\Dapper.dll</HintPath>
<Reference Include="CsvHelper, Version=33.0.0.0, Culture=neutral, PublicKeyToken=8c4a6d608ce8f59c, processorArchitecture=MSIL">
<HintPath>..\packages\CsvHelper.33.0.1\lib\net481\CsvHelper.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.0-rc.1.25451.107\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.0-preview.4.25258.110\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.HashCode, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.HashCode.6.0.0\lib\net462\Microsoft.Bcl.HashCode.dll</HintPath>
<HintPath>..\packages\Microsoft.Bcl.HashCode.6.0.0\lib\net461\Microsoft.Bcl.HashCode.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.Numerics, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.Numerics.10.0.0-rc.1.25451.107\lib\net462\Microsoft.Bcl.Numerics.dll</HintPath>
<HintPath>..\packages\Microsoft.Bcl.Numerics.10.0.0-preview.4.25258.110\lib\net462\Microsoft.Bcl.Numerics.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.Core.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.CpuMath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.CpuMath.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.CpuMath.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.Data.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Data.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.DataView, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.DataView.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.DataView.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.DataView.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.DataView.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.FastTree, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.FastTree.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.FastTree.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.KMeansClustering, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.KMeansClustering.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.KMeansClustering.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.PCA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.PCA.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.PCA.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.StandardTrainers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.StandardTrainers.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.StandardTrainers.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.Transforms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.25503.2\lib\netstandard2.0\Microsoft.ML.Transforms.dll</HintPath>
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Transforms.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="RestSharp, Version=112.1.1.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.112.1.1-alpha.0.4\lib\net48\RestSharp.dll</HintPath>
<HintPath>..\packages\RestSharp.112.1.0\lib\net481\RestSharp.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="WindowsBase" />
<Reference Include="System.Xaml" />
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.CodeDom, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.CodeDom.10.0.0-rc.1.25451.107\lib\net462\System.CodeDom.dll</HintPath>
<HintPath>..\packages\System.CodeDom.10.0.0-preview.4.25258.110\lib\net462\System.CodeDom.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.10.0.0-rc.1.25451.107\lib\net462\System.Collections.Immutable.dll</HintPath>
<HintPath>..\packages\System.Collections.Immutable.10.0.0-preview.4.25258.110\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.IO.Pipelines, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Pipelines.10.0.0-rc.1.25451.107\lib\net462\System.IO.Pipelines.dll</HintPath>
<HintPath>..\packages\System.IO.Pipelines.10.0.0-preview.4.25258.110\lib\net462\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Tensors, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Tensors.10.0.0-rc.1.25451.107\lib\net462\System.Numerics.Tensors.dll</HintPath>
<HintPath>..\packages\System.Numerics.Tensors.10.0.0-preview.4.25258.110\lib\net462\System.Numerics.Tensors.dll</HintPath>
</Reference>
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
@@ -135,13 +132,13 @@
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.10.0.0-rc.1.25451.107\lib\net462\System.Text.Encodings.Web.dll</HintPath>
<HintPath>..\packages\System.Text.Encodings.Web.10.0.0-preview.4.25258.110\lib\net462\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Json.10.0.0-rc.1.25451.107\lib\net462\System.Text.Json.dll</HintPath>
<HintPath>..\packages\System.Text.Json.10.0.0-preview.4.25258.110\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Channels, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Channels.10.0.0-rc.1.25451.107\lib\net462\System.Threading.Channels.dll</HintPath>
<HintPath>..\packages\System.Threading.Channels.10.0.0-preview.4.25258.110\lib\net462\System.Threading.Channels.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
@@ -151,13 +148,27 @@
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="CsvHelperStubs.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Football\Database\BetType.cs" />
<Compile Include="Football\Database\Bookmaker.cs" />
<Compile Include="Football\Database\APIResponse.cs" />
@@ -179,6 +190,8 @@
<Compile Include="Football\Database\Prediction.cs" />
<Compile Include="Football\Database\Score.cs" />
<Compile Include="Football\Database\Team.cs" />
<Compile Include="HorseRacing\API\RacingApiClient.cs" />
<Compile Include="HorseRacing\Main.cs" />
<Compile Include="Horses\Calculator.cs" />
<Compile Include="Horses\Files\Maps\Punters.cs" />
<Compile Include="Horses\ML\HorseRacePrediction.cs" />
@@ -192,42 +205,7 @@
<Compile Include="Horses\FileReader.cs" />
<Compile Include="Horses\Database.cs" />
<Compile Include="Horses\Files\Punters.cs" />
<Compile Include="Main.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Main.Designer.cs">
<DependentUpon>Main.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UI\Controls\ModernButton.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\Controls\ModernDataGridView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\Controls\ModernDateTimePicker.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\Controls\ModernLabel.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\Controls\ModernPanel.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\Controls\ModernProgressBar.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\Controls\ModernTabControl.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\Controls\ModernTextBox.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\ModernTheme.cs" />
<EmbeddedResource Include="Main.resx">
<DependentUpon>Main.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
@@ -263,13 +241,13 @@
<Import Project="..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>Questo progetto fa riferimento a uno o più pacchetti NuGet che non sono presenti in questo computer. Usare lo strumento di ripristino dei pacchetti NuGet per scaricarli. Per altre informazioni, vedere http://go.microsoft.com/fwlink/?LinkID=322105. Il file mancante è {0}.</ErrorText>
<ErrorText>Questo progetto fa riferimento a uno o pi<EFBFBD> pacchetti NuGet che non sono presenti in questo computer. Usare lo strumento di ripristino dei pacchetti NuGet per scaricarli. Per altre informazioni, vedere http://go.microsoft.com/fwlink/?LinkID=322105. Il file mancante <EFBFBD> {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props'))" />
</Target>
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets')" />
</Project>

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Globalization;
// Minimal compatibility stubs for CsvHelper types used in the project.
// These are light-weight placeholders so the project can compile when the CsvHelper NuGet
// package is not restored. They intentionally provide only the members the project expects.
namespace CsvHelper.Configuration.Attributes
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class NameAttribute : Attribute
{
public string[] Names { get; }
public NameAttribute(params string[] names) => Names = names ?? Array.Empty<string>();
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class TypeConverterAttribute : Attribute
{
public Type ConverterType { get; }
public TypeConverterAttribute(Type converterType) => ConverterType = converterType;
}
}
namespace CsvHelper.TypeConversion
{
public class DefaultTypeConverter
{
// Signature matches CsvHelper's DefaultTypeConverter override used in the project
public virtual object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) => (object)text;
}
public class TypeConverterCache
{
private readonly Dictionary<Type, object> _converters = new Dictionary<Type, object>();
public void AddConverter<T>(DefaultTypeConverter converter) => _converters[typeof(T)] = converter;
}
// Minimal placeholders referenced in code
public interface IReaderRow { }
public class MemberMapData { }
}
namespace CsvHelper.Configuration
{
public enum TrimOptions { None, Trim }
public class CsvConfiguration
{
public CsvConfiguration(CultureInfo culture) { Culture = culture; }
public CultureInfo Culture { get; }
public bool HasHeaderRecord { get; set; }
public Func<PrepareHeaderForMatchArgs, string> PrepareHeaderForMatch { get; set; }
public object HeaderValidated { get; set; }
public Action<object> MissingFieldFound { get; set; }
public Action<object> BadDataFound { get; set; }
public TrimOptions TrimOptions { get; set; }
public string Delimiter { get; set; }
}
// Basic ClassMap and mapping helper used by the project's mapping files
public class ClassMap<T>
{
public MemberMap Map(Func<T, object> func) => new MemberMap();
}
public class MemberMap
{
public MemberMap Name(params string[] names) { return this; }
}
// Minimal args used by PrepareHeaderForMatch delegate in code above
public class PrepareHeaderForMatchArgs
{
public string Header { get; set; }
}
}
namespace CsvHelper
{
using CsvHelper.Configuration;
using CsvHelper.TypeConversion;
public class CsvReader : IDisposable
{
private readonly TextReader _reader;
public CsvReader(TextReader reader, CsvConfiguration config)
{
_reader = reader;
Configuration = config;
Context = new ReaderContext();
}
public CsvConfiguration Configuration { get; }
public ReaderContext Context { get; }
public void Dispose() { /* nothing to dispose in stub */ }
public bool Read() => false;
public void ReadHeader() { }
public string[] HeaderRecord => Array.Empty<string>();
public string GetField(int index) => null;
public IEnumerable<T> GetRecords<T>() { return Enumerable.Empty<T>(); }
public class ReaderContext
{
public TypeConverterCache TypeConverterCache { get; } = new TypeConverterCache();
public void RegisterClassMap<TMap>() { }
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -2,11 +2,12 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RestSharp;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Data.SqlClient;
namespace HorseRacingPredictor.Football
{
@@ -96,6 +97,42 @@ namespace HorseRacingPredictor.Football
}
}
/// <summary>
/// Recupera solo l'elenco delle partite per la data specificata e le restituisce come DataTable semplice
/// </summary>
public DataTable GetTodayFixtures(DateTime date, IProgress<int> progressCallback = null, IProgress<string> statusCallback = null)
{
try
{
statusCallback?.Report("Scaricamento elenco partite...");
progressCallback?.Report(10);
var fixturesResponse = GetFixtures(date);
progressCallback?.Report(40);
statusCallback?.Report("Elaborazione partite...");
var table = CreateFixturesDataTable(fixturesResponse);
progressCallback?.Report(50);
statusCallback?.Report("Scaricamento quote...");
var oddsResponses = GetOdds(date);
progressCallback?.Report(80);
statusCallback?.Report("Integrazione quote...");
ParseOddsIntoTable(table, oddsResponses);
progressCallback?.Report(100);
statusCallback?.Report($"Trovate {table.Rows.Count} partite con quote");
return table;
}
catch (Exception ex)
{
_database.LogError("recupero partite del giorno", ex);
statusCallback?.Report($"Errore: {ex.Message}");
return CreateEmptyResultTable();
}
}
/// <summary>
/// Scarica i dati dalle API e li salva nella tabella di frontiera API_Response
/// </summary>
@@ -155,20 +192,20 @@ namespace HorseRacingPredictor.Football
return;
}
var json = JObject.Parse(fixturesResponse.Content);
var json = JsonDocument.Parse(fixturesResponse.Content).RootElement;
// Verifica che la risposta contenga dati validi
if (json["response"] == null)
if (!json.TryGetProperty("response", out var responseElement))
{
return;
}
// Per ogni partita, recupera la previsione
foreach (var item in json["response"])
foreach (var item in responseElement.EnumerateArray())
{
try
{
int fixtureId = item["fixture"]["id"].Value<int>();
int fixtureId = item.GetProperty("fixture").GetProperty("id").GetInt32();
// Utilizza il prediction manager per ottenere la previsione
_predictionManager.GetPredictionByFixture(fixtureId);
@@ -254,10 +291,11 @@ namespace HorseRacingPredictor.Football
responses.Add(response);
// Controlla se ci sono altre pagine
var json = JObject.Parse(response.Content);
if (json["paging"] != null && json["paging"]["total"] != null)
var json = JsonDocument.Parse(response.Content).RootElement;
if (json.TryGetProperty("paging", out var pagingElement) &&
pagingElement.TryGetProperty("total", out var totalElement))
{
int totalPages = json["paging"]["total"].Value<int>();
int totalPages = totalElement.GetInt32();
hasMorePages = currentPage < totalPages;
}
else
@@ -279,7 +317,7 @@ namespace HorseRacingPredictor.Football
{
try
{
var jsonObject = JObject.Parse(jsonResponse);
var jsonObject = JsonNode.Parse(jsonResponse);
_database.ExecuteTransactionalQuery("l'elaborazione dei dati calcistici", (connection, transaction) =>
{
@@ -289,7 +327,7 @@ namespace HorseRacingPredictor.Football
((Manager.Database)_database).DisableAllConstraints(connection, transaction);
// FASE 1: Inserisci le leghe
foreach (var responseItem in jsonObject["response"])
foreach (var responseItem in jsonObject["response"].AsArray())
{
var league = responseItem["league"];
if (league != null && league["id"] != null)
@@ -299,11 +337,11 @@ namespace HorseRacingPredictor.Football
}
// FASE 2: Inserisci i bookmakers e i tipi di scommessa
foreach (var responseItem in jsonObject["response"])
foreach (var responseItem in jsonObject["response"].AsArray())
{
if (responseItem["bookmakers"] != null)
{
foreach (var bookmaker in responseItem["bookmakers"])
foreach (var bookmaker in responseItem["bookmakers"].AsArray())
{
if (bookmaker["id"] != null)
{
@@ -312,7 +350,7 @@ namespace HorseRacingPredictor.Football
// Tipi di scommessa
if (bookmaker["bets"] != null)
{
foreach (var bet in bookmaker["bets"])
foreach (var bet in bookmaker["bets"].AsArray())
{
if (bet["id"] != null && bet["name"] != null)
{
@@ -326,7 +364,7 @@ namespace HorseRacingPredictor.Football
}
// FASE 3: Inserisci le squadre (senza relazione con fixture)
foreach (var responseItem in jsonObject["response"])
foreach (var responseItem in jsonObject["response"].AsArray())
{
var teams = responseItem["teams"];
if (teams != null && teams["home"] != null && teams["away"] != null &&
@@ -338,7 +376,7 @@ namespace HorseRacingPredictor.Football
}
// FASE 4: Inserisci i fixture e venue
foreach (var responseItem in jsonObject["response"])
foreach (var responseItem in jsonObject["response"].AsArray())
{
var fixture = responseItem["fixture"];
if (fixture != null && fixture["id"] != null)
@@ -348,7 +386,7 @@ namespace HorseRacingPredictor.Football
}
// FASE 5: Inserisci relazioni tra entità e dati dipendenti
foreach (var responseItem in jsonObject["response"])
foreach (var responseItem in jsonObject["response"].AsArray())
{
int? fixtureId = null;
try
@@ -356,7 +394,7 @@ namespace HorseRacingPredictor.Football
var fixture = responseItem["fixture"];
if (fixture != null && fixture["id"] != null)
{
fixtureId = fixture["id"].Value<int>();
fixtureId = fixture["id"].GetValue<int>();
// Relazioni fixture-team
var teams = responseItem["teams"];
@@ -370,7 +408,7 @@ namespace HorseRacingPredictor.Football
var league = responseItem["league"];
if (league != null && league["id"] != null)
{
int leagueId = league["id"].Value<int>();
int leagueId = league["id"].GetValue<int>();
_fixtureLeagueRepository.Upsert(connection, transaction, fixtureId.Value, leagueId, league);
}
@@ -397,7 +435,7 @@ namespace HorseRacingPredictor.Football
}
// FASE 6: Inserisci dati che richiedono fixture e teams: quote e previsioni
foreach (var responseItem in jsonObject["response"])
foreach (var responseItem in jsonObject["response"].AsArray())
{
int? fixtureId = null;
try
@@ -405,7 +443,7 @@ namespace HorseRacingPredictor.Football
var fixture = responseItem["fixture"];
if (fixture != null && fixture["id"] != null)
{
fixtureId = fixture["id"].Value<int>();
fixtureId = fixture["id"].GetValue<int>();
// Quote
if (responseItem["bookmakers"] != null)
@@ -440,15 +478,15 @@ namespace HorseRacingPredictor.Football
}
// Head-to-head
if (predictions["h2h"] != null && predictions["h2h"].Any())
if (predictions["h2h"] != null && predictions["h2h"].AsArray().Any())
{
_h2hRepository.DeleteForPrediction(connection, predictionId.Value);
foreach (var h2hFixture in predictions["h2h"])
foreach (var h2hFixture in predictions["h2h"].AsArray())
{
if (h2hFixture["fixture"] != null && h2hFixture["fixture"]["id"] != null)
{
_h2hRepository.Insert(connection, predictionId.Value, h2hFixture["fixture"]["id"].Value<int>());
_h2hRepository.Insert(connection, predictionId.Value, h2hFixture["fixture"]["id"].GetValue<int>());
}
}
}
@@ -460,7 +498,7 @@ namespace HorseRacingPredictor.Football
// Home team stats
if (teams["home"] != null && teams["home"]["id"] != null && predictions["teams"]["home"] != null)
{
int homeTeamId = teams["home"]["id"].Value<int>();
int homeTeamId = teams["home"]["id"].GetValue<int>();
if (predictions["teams"]["home"]["team"] != null)
{
@@ -478,7 +516,7 @@ namespace HorseRacingPredictor.Football
// Away team stats
if (teams["away"] != null && teams["away"]["id"] != null && predictions["teams"]["away"] != null)
{
int awayTeamId = teams["away"]["id"].Value<int>();
int awayTeamId = teams["away"]["id"].GetValue<int>();
if (predictions["teams"]["away"]["team"] != null)
{
@@ -561,6 +599,13 @@ namespace HorseRacingPredictor.Football
dataTable.Columns.Add("Quota Casa", typeof(string));
dataTable.Columns.Add("Quota Pareggio", typeof(string));
dataTable.Columns.Add("Quota Trasferta", typeof(string));
dataTable.Columns.Add("Over 2.5", typeof(string));
dataTable.Columns.Add("Under 2.5", typeof(string));
dataTable.Columns.Add("BTTS Sì", typeof(string));
dataTable.Columns.Add("BTTS No", typeof(string));
dataTable.Columns.Add("Doppia Casa/X", typeof(string));
dataTable.Columns.Add("Doppia Casa/Trasf", typeof(string));
dataTable.Columns.Add("Doppia X/Trasf", typeof(string));
dataTable.Columns.Add("Previsione", typeof(string));
return dataTable;
@@ -581,41 +626,78 @@ namespace HorseRacingPredictor.Football
try
{
var json = JObject.Parse(response.Content);
var json = JsonDocument.Parse(response.Content).RootElement;
// Verifica che la risposta contenga dati validi
if (json["response"] == null)
if (!json.TryGetProperty("response", out var responseElement))
{
return dataTable;
}
// Aggiungi righe
foreach (var item in json["response"])
foreach (var item in responseElement.EnumerateArray())
{
try
{
// Verifica che le proprietà essenziali esistano
if (!item.TryGetProperty("fixture", out var fixtureEl) ||
!item.TryGetProperty("league", out var leagueEl) ||
!item.TryGetProperty("teams", out var teamsEl))
{
continue;
}
var row = dataTable.NewRow();
// Estrai i dati dalla risposta JSON
row["ID"] = item["fixture"]["id"].Value<int>();
row["Paese"] = item["league"]["country"].Value<string>();
row["Campionato"] = item["league"]["name"].Value<string>();
row["Data / Ora"] = DateTime.Parse(item["fixture"]["date"].Value<string>());
row["Stato"] = item["fixture"]["status"]["long"].Value<string>();
row["Casa"] = item["teams"]["home"]["name"].Value<string>();
row["Trasferta"] = item["teams"]["away"]["name"].Value<string>();
row["ID"] = fixtureEl.TryGetProperty("id", out var idEl) ? idEl.GetInt32() : 0;
row["Paese"] = leagueEl.TryGetProperty("country", out var countryEl) ? countryEl.GetString() ?? "" : "";
row["Campionato"] = leagueEl.TryGetProperty("name", out var leagueNameEl) ? leagueNameEl.GetString() ?? "" : "";
// Per i goals, controlla se sono null
var goalsElement = item["goals"];
row["Goals Casa"] = goalsElement["home"].Type == JTokenType.Null ?
0 : goalsElement["home"].Value<int>();
row["Goals Trasferta"] = goalsElement["away"].Type == JTokenType.Null ?
0 : goalsElement["away"].Value<int>();
if (fixtureEl.TryGetProperty("date", out var dateEl) && dateEl.ValueKind == JsonValueKind.String)
{
DateTime.TryParse(dateEl.GetString(), out var parsedDate);
row["Data / Ora"] = parsedDate;
}
else
{
row["Data / Ora"] = DBNull.Value;
}
row["Stato"] = fixtureEl.TryGetProperty("status", out var statusEl) &&
statusEl.TryGetProperty("long", out var statusLong)
? statusLong.GetString() ?? "" : "";
row["Casa"] = teamsEl.TryGetProperty("home", out var homeEl) &&
homeEl.TryGetProperty("name", out var homeNameEl)
? homeNameEl.GetString() ?? "" : "";
row["Trasferta"] = teamsEl.TryGetProperty("away", out var awayEl) &&
awayEl.TryGetProperty("name", out var awayNameEl)
? awayNameEl.GetString() ?? "" : "";
// Goals (possono essere null per partite non iniziate)
if (item.TryGetProperty("goals", out var goalsElement))
{
row["Goals Casa"] = goalsElement.TryGetProperty("home", out var ghEl) && ghEl.ValueKind == JsonValueKind.Number
? ghEl.GetInt32() : 0;
row["Goals Trasferta"] = goalsElement.TryGetProperty("away", out var gaEl) && gaEl.ValueKind == JsonValueKind.Number
? gaEl.GetInt32() : 0;
}
else
{
row["Goals Casa"] = 0;
row["Goals Trasferta"] = 0;
}
// Le colonne delle quote e previsione rimangono temporaneamente vuote
row["Quota Casa"] = DBNull.Value;
row["Quota Pareggio"] = DBNull.Value;
row["Quota Trasferta"] = DBNull.Value;
row["Over 2.5"] = DBNull.Value;
row["Under 2.5"] = DBNull.Value;
row["BTTS Sì"] = DBNull.Value;
row["BTTS No"] = DBNull.Value;
row["Doppia Casa/X"] = DBNull.Value;
row["Doppia Casa/Trasf"] = DBNull.Value;
row["Doppia X/Trasf"] = DBNull.Value;
row["Previsione"] = DBNull.Value;
dataTable.Rows.Add(row);
@@ -623,14 +705,12 @@ namespace HorseRacingPredictor.Football
catch (Exception ex)
{
_database.LogError($"elaborazione riga partita", ex);
// Continua con la prossima partita
}
}
}
catch (Exception ex)
{
_database.LogError("creazione tabella partite", ex);
// Ritorna la tabella vuota in caso di errore
}
return dataTable;
@@ -660,18 +740,18 @@ namespace HorseRacingPredictor.Football
continue;
}
var json = JObject.Parse(response.Content);
var json = JsonDocument.Parse(response.Content).RootElement;
if (json["response"] == null)
if (!json.TryGetProperty("response", out var responseElement))
{
continue;
}
foreach (var item in json["response"])
foreach (var item in responseElement.EnumerateArray())
{
try
{
int fixtureId = item["fixture"]["id"].Value<int>();
int fixtureId = item.GetProperty("fixture").GetProperty("id").GetInt32();
// Trova la riga corrispondente nella tabella delle partite
DataRow[] matchingRows = combinedTable.Select($"ID = {fixtureId}");
@@ -680,23 +760,23 @@ namespace HorseRacingPredictor.Football
DataRow row = matchingRows[0];
// Cerca le quote del bookmaker (assumiamo Bet365 con ID 8)
if (item["bookmakers"] != null)
if (item.TryGetProperty("bookmakers", out var bookmakersElement))
{
foreach (var bookmaker in item["bookmakers"])
foreach (var bookmaker in bookmakersElement.EnumerateArray())
{
if (bookmaker["id"].Value<int>() == 8) // Bet365
if (bookmaker.GetProperty("id").GetInt32() == 8) // Bet365
{
if (bookmaker["bets"] != null)
if (bookmaker.TryGetProperty("bets", out var betsElement))
{
foreach (var bet in bookmaker["bets"])
foreach (var bet in betsElement.EnumerateArray())
{
if (bet["name"].Value<string>() == "Match Winner")
if (bet.GetProperty("name").GetString() == "Match Winner")
{
// Elabora le quote 1X2
foreach (var value in bet["values"])
foreach (var value in bet.GetProperty("values").EnumerateArray())
{
string betType = value["value"].Value<string>();
string odd = value["odd"].Value<string>();
string betType = value.GetProperty("value").GetString();
string odd = value.GetProperty("odd").GetString();
switch (betType)
{
@@ -727,15 +807,16 @@ namespace HorseRacingPredictor.Football
var predictionResponse = _predictionManager.GetPredictionByFixture(fixtureId);
if (predictionResponse != null && predictionResponse.IsSuccessful)
{
var predictionJson = JObject.Parse(predictionResponse.Content);
if (predictionJson["response"] != null && predictionJson["response"].Any())
var predictionJson = JsonDocument.Parse(predictionResponse.Content).RootElement;
if (predictionJson.TryGetProperty("response", out var predResponse) &&
predResponse.EnumerateArray().Any())
{
var prediction = predictionJson["response"].First;
if (prediction["predictions"] != null &&
prediction["predictions"]["winner"] != null &&
prediction["predictions"]["winner"]["name"] != null)
var prediction = predResponse.EnumerateArray().First();
if (prediction.TryGetProperty("predictions", out var predsElement) &&
predsElement.TryGetProperty("winner", out var winnerElement) &&
winnerElement.TryGetProperty("name", out var winnerNameElement))
{
row["Previsione"] = prediction["predictions"]["winner"]["name"].Value<string>();
row["Previsione"] = winnerNameElement.GetString();
}
}
}
@@ -785,6 +866,162 @@ namespace HorseRacingPredictor.Football
return table;
}
/// <summary>
/// Analizza le risposte delle quote dall'API e le inserisce direttamente nella DataTable delle partite.
/// Utilizza Bet365 (ID 8) come bookmaker principale; se non presente, usa il primo bookmaker disponibile.
/// </summary>
private void ParseOddsIntoTable(DataTable fixturesTable, List<RestResponse> oddsResponses)
{
if (oddsResponses == null || oddsResponses.Count == 0 || fixturesTable == null || fixturesTable.Rows.Count == 0)
return;
try
{
foreach (var response in oddsResponses)
{
if (response == null || !response.IsSuccessful || string.IsNullOrEmpty(response.Content))
continue;
var json = JsonDocument.Parse(response.Content).RootElement;
if (!json.TryGetProperty("response", out var responseElement))
continue;
foreach (var item in responseElement.EnumerateArray())
{
try
{
if (!item.TryGetProperty("fixture", out var fixtureEl))
continue;
int fixtureId = fixtureEl.GetProperty("id").GetInt32();
DataRow[] matchingRows = fixturesTable.Select($"ID = {fixtureId}");
if (matchingRows.Length == 0)
continue;
DataRow row = matchingRows[0];
if (!item.TryGetProperty("bookmakers", out var bookmakersEl))
continue;
// Cerca Bet365 (ID 8), altrimenti usa il primo bookmaker
JsonElement selectedBookmaker = default;
bool found = false;
foreach (var bm in bookmakersEl.EnumerateArray())
{
if (bm.TryGetProperty("id", out var bmId) && bmId.GetInt32() == 8)
{
selectedBookmaker = bm;
found = true;
break;
}
}
if (!found)
{
var enumerator = bookmakersEl.EnumerateArray();
if (enumerator.MoveNext())
{
selectedBookmaker = enumerator.Current;
found = true;
}
}
if (!found || !selectedBookmaker.TryGetProperty("bets", out var betsEl))
continue;
foreach (var bet in betsEl.EnumerateArray())
{
string betName = bet.TryGetProperty("name", out var nameEl) ? nameEl.GetString() : "";
if (string.IsNullOrEmpty(betName) || !bet.TryGetProperty("values", out var valuesEl))
continue;
switch (betName)
{
case "Match Winner":
foreach (var v in valuesEl.EnumerateArray())
{
string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd");
if (val == "Home") row["Quota Casa"] = odd;
else if (val == "Draw") row["Quota Pareggio"] = odd;
else if (val == "Away") row["Quota Trasferta"] = odd;
}
break;
case "Goals Over/Under":
foreach (var v in valuesEl.EnumerateArray())
{
string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd");
if (val == "Over 2.5") row["Over 2.5"] = odd;
else if (val == "Under 2.5") row["Under 2.5"] = odd;
}
break;
case "Both Teams Score":
foreach (var v in valuesEl.EnumerateArray())
{
string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd");
if (val == "Yes") row["BTTS Sì"] = odd;
else if (val == "No") row["BTTS No"] = odd;
}
break;
case "Double Chance":
foreach (var v in valuesEl.EnumerateArray())
{
string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd");
if (val == "Home/Draw") row["Doppia Casa/X"] = odd;
else if (val == "Home/Away") row["Doppia Casa/Trasf"] = odd;
else if (val == "Draw/Away") row["Doppia X/Trasf"] = odd;
}
break;
}
}
}
catch (Exception ex)
{
_database.LogError("elaborazione quote per singola partita", ex);
}
}
}
}
catch (Exception ex)
{
_database.LogError("parsing quote nella tabella", ex);
}
}
/// <summary>
/// Estrae un valore stringa da un JsonElement in modo sicuro
/// </summary>
private string GetOddValueString(JsonElement element, string propertyName)
{
try
{
if (!element.TryGetProperty(propertyName, out var prop))
return "";
switch (prop.ValueKind)
{
case JsonValueKind.String:
return prop.GetString() ?? "";
case JsonValueKind.Number:
return prop.GetDecimal().ToString(System.Globalization.CultureInfo.InvariantCulture);
default:
return prop.ToString();
}
}
catch
{
return "";
}
}
/// <summary>
/// Elabora le risposte API non elaborate presenti nel database
/// </summary>

View File

@@ -39,54 +39,68 @@ namespace HorseRacingPredictor.Football.Manager
/// Esegue una richiesta API, salva la risposta nel database e restituisce la risposta
/// </summary>
protected RestResponse ExecuteRequest(string endpoint, string apiType = null, string parameters = null)
{
try
{
string url = $"{BaseUrl}/{endpoint}";
var response = ExecuteApiRequest(url, ApiKey, KeyHeader, HostHeader, HostValue);
// Salva la risposta su file per debug
SaveResponseToFile(url, response.Content, "FootballApiResponses");
// Salva la risposta nel database
if (apiType != null)
// Salva la risposta su file per debug (non bloccare in caso di errore)
try
{
_apiResponseRepository.InsertApiResponse(apiType, endpoint, parameters, response);
}
return response;
SaveResponseToFile(url, response.Content, "FootballApiResponses");
}
catch (Exception ex)
{
throw new Exception($"Errore durante la richiesta all'endpoint {endpoint}: {ex.Message}", ex);
Console.WriteLine($"Avviso: impossibile salvare risposta su file: {ex.Message}");
}
// Salva la risposta nel database (non bloccare in caso di errore)
if (apiType != null)
{
try
{
_apiResponseRepository.InsertApiResponse(apiType, endpoint, parameters, response);
}
catch (Exception ex)
{
Console.WriteLine($"Avviso: impossibile salvare risposta nel database: {ex.Message}");
}
}
return response;
}
/// <summary>
/// Versione asincrona di ExecuteRequest
/// </summary>
protected async Task<RestResponse> ExecuteRequestAsync(string endpoint, string apiType = null, string parameters = null)
{
try
{
string url = $"{BaseUrl}/{endpoint}";
var response = await ExecuteApiRequestAsync(url, ApiKey, KeyHeader, HostHeader, HostValue);
// Salva la risposta su file per debug
SaveResponseToFile(url, response.Content, "FootballApiResponses");
// Salva la risposta nel database
if (apiType != null)
// Salva la risposta su file per debug (non bloccare in caso di errore)
try
{
_apiResponseRepository.InsertApiResponse(apiType, endpoint, parameters, response);
}
return response;
SaveResponseToFile(url, response.Content, "FootballApiResponses");
}
catch (Exception ex)
{
throw new Exception($"Errore durante la richiesta all'endpoint {endpoint}: {ex.Message}", ex);
Console.WriteLine($"Avviso: impossibile salvare risposta su file: {ex.Message}");
}
// Salva la risposta nel database (non bloccare in caso di errore)
if (apiType != null)
{
try
{
_apiResponseRepository.InsertApiResponse(apiType, endpoint, parameters, response);
}
catch (Exception ex)
{
Console.WriteLine($"Avviso: impossibile salvare risposta nel database: {ex.Message}");
}
}
return response;
}
#region Leagues

View File

@@ -0,0 +1,136 @@
using System;
using System.Text;
using System.Threading;
using RestSharp;
namespace HorseRacingPredictor.HorseRacing.API
{
/// <summary>
/// Client per The Racing API (theracingapi.com)
/// Utilizza HTTP Basic Authentication
/// </summary>
internal class RacingApiClient
{
private const string BaseUrl = "https://api.theracingapi.com/v1";
private const int DefaultDelay = 1100; // Rate limit: 1 req/sec per Free plan
private readonly string _username;
private readonly string _password;
public RacingApiClient(string username, string password)
{
_username = username;
_password = password;
}
/// <summary>
/// Esegue una richiesta GET autenticata con HTTP Basic Auth
/// </summary>
private RestResponse ExecuteRequest(string endpoint, int delay = DefaultDelay)
{
string url = $"{BaseUrl}/{endpoint}";
var client = new RestClient(url);
var request = new RestRequest();
string credentials = Convert.ToBase64String(
Encoding.ASCII.GetBytes($"{_username}:{_password}"));
request.AddHeader("Authorization", $"Basic {credentials}");
try
{
var response = client.Execute(request);
if (!response.IsSuccessful)
{
throw new Exception(
$"Errore API Racing ({(int)response.StatusCode}): {response.StatusDescription}");
}
if (delay > 0)
Thread.Sleep(delay);
return response;
}
catch (Exception ex) when (!(ex.Message.StartsWith("Errore API Racing")))
{
throw new Exception($"Errore durante la richiesta a Racing API: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene le racecard (programma corse) per oggi o domani
/// </summary>
/// <param name="day">"today" oppure "tomorrow"</param>
/// <param name="regionCodes">Codici regione opzionali (es. "gb", "ire")</param>
public RestResponse GetRacecardsFree(string day = "today", string[] regionCodes = null)
{
var sb = new StringBuilder("racecards/free?");
sb.Append($"day={day}");
if (regionCodes != null && regionCodes.Length > 0)
{
foreach (var rc in regionCodes)
sb.Append($"&region_codes={rc}");
}
return ExecuteRequest(sb.ToString());
}
/// <summary>
/// Ottiene le racecard standard per oggi o domani
/// </summary>
public RestResponse GetRacecardsStandard(string day = "today", string[] regionCodes = null)
{
var sb = new StringBuilder("racecards/standard?");
sb.Append($"day={day}");
if (regionCodes != null && regionCodes.Length > 0)
{
foreach (var rc in regionCodes)
sb.Append($"&region_codes={rc}");
}
return ExecuteRequest(sb.ToString());
}
/// <summary>
/// Ottiene i risultati per un intervallo di date
/// </summary>
public RestResponse GetResults(DateTime startDate, DateTime endDate, string[] regionCodes = null)
{
var sb = new StringBuilder("results?");
sb.Append($"start_date={startDate:yyyy-MM-dd}");
sb.Append($"&end_date={endDate:yyyy-MM-dd}");
if (regionCodes != null && regionCodes.Length > 0)
{
foreach (var rc in regionCodes)
sb.Append($"&region={rc}");
}
return ExecuteRequest(sb.ToString());
}
/// <summary>
/// Ottiene l'elenco delle regioni disponibili
/// </summary>
public RestResponse GetRegions()
{
return ExecuteRequest("courses/regions");
}
/// <summary>
/// Ottiene l'elenco dei corsi per le regioni specificate
/// </summary>
public RestResponse GetCourses(string[] regionCodes = null)
{
var sb = new StringBuilder("courses?");
if (regionCodes != null && regionCodes.Length > 0)
{
foreach (var rc in regionCodes)
sb.Append($"&region_codes={rc}");
}
return ExecuteRequest(sb.ToString());
}
}
}

View File

@@ -0,0 +1,266 @@
using System;
using System.Data;
using System.Text.Json;
using HorseRacingPredictor.HorseRacing.API;
namespace HorseRacingPredictor.HorseRacing
{
/// <summary>
/// Gestore centralizzato per la sezione Corse dei Cavalli.
/// Scarica i dati da The Racing API e li converte in DataTable.
/// </summary>
public class Main
{
private RacingApiClient _client;
public Main(string username, string password)
{
_client = new RacingApiClient(username, password);
}
/// <summary>
/// Aggiorna le credenziali API
/// </summary>
public void UpdateCredentials(string username, string password)
{
_client = new RacingApiClient(username, password);
}
/// <summary>
/// Scarica le racecard (programma corse) per oggi o domani e le restituisce come DataTable
/// </summary>
public DataTable GetRacecards(string day, IProgress<int> progress = null, IProgress<string> status = null)
{
try
{
status?.Report("Connessione a The Racing API...");
progress?.Report(10);
var response = _client.GetRacecardsFree(day);
progress?.Report(60);
status?.Report("Elaborazione racecard...");
var table = ParseRacecardsResponse(response.Content);
progress?.Report(100);
status?.Report($"Trovate {table.Rows.Count} corse");
return table;
}
catch (Exception ex)
{
status?.Report($"Errore: {ex.Message}");
return CreateEmptyRacecardsTable();
}
}
/// <summary>
/// Scarica i risultati per un intervallo di date
/// </summary>
public DataTable GetResults(DateTime startDate, DateTime endDate,
IProgress<int> progress = null, IProgress<string> status = null)
{
try
{
status?.Report("Scaricamento risultati...");
progress?.Report(10);
var response = _client.GetResults(startDate, endDate);
progress?.Report(60);
status?.Report("Elaborazione risultati...");
var table = ParseResultsResponse(response.Content);
progress?.Report(100);
status?.Report($"Trovati {table.Rows.Count} risultati");
return table;
}
catch (Exception ex)
{
status?.Report($"Errore: {ex.Message}");
return CreateEmptyResultsTable();
}
}
#region DataTable creation
private DataTable CreateEmptyRacecardsTable()
{
var dt = new DataTable();
dt.Columns.Add("Ora", typeof(string));
dt.Columns.Add("Ippodromo", typeof(string));
dt.Columns.Add("Regione", typeof(string));
dt.Columns.Add("Corsa", typeof(string));
dt.Columns.Add("Distanza", typeof(string));
dt.Columns.Add("Tipo", typeof(string));
dt.Columns.Add("Classe", typeof(string));
dt.Columns.Add("Terreno", typeof(string));
dt.Columns.Add("N. Corridori", typeof(int));
dt.Columns.Add("Età", typeof(string));
dt.Columns.Add("Premio", typeof(string));
return dt;
}
private DataTable CreateEmptyResultsTable()
{
var dt = new DataTable();
dt.Columns.Add("Data", typeof(string));
dt.Columns.Add("Ippodromo", typeof(string));
dt.Columns.Add("Corsa", typeof(string));
dt.Columns.Add("Distanza", typeof(string));
dt.Columns.Add("Terreno", typeof(string));
dt.Columns.Add("1° Classificato", typeof(string));
dt.Columns.Add("2° Classificato", typeof(string));
dt.Columns.Add("3° Classificato", typeof(string));
dt.Columns.Add("Fantino 1°", typeof(string));
dt.Columns.Add("SP 1°", typeof(string));
return dt;
}
#endregion
#region JSON Parsing
private DataTable ParseRacecardsResponse(string json)
{
var dt = CreateEmptyRacecardsTable();
if (string.IsNullOrEmpty(json)) return dt;
try
{
using (var doc = JsonDocument.Parse(json))
{
var root = doc.RootElement;
if (!root.TryGetProperty("racecards", out var racecardsEl))
return dt;
foreach (var rc in racecardsEl.EnumerateArray())
{
try
{
var row = dt.NewRow();
row["Ora"] = GetString(rc, "off_time", "");
row["Ippodromo"] = GetString(rc, "course", "");
row["Regione"] = GetString(rc, "region", "");
row["Corsa"] = GetString(rc, "race_name", "");
row["Distanza"] = GetString(rc, "distance", "");
row["Tipo"] = GetString(rc, "type", "");
row["Classe"] = GetString(rc, "race_class", "");
row["Terreno"] = GetString(rc, "going", "");
row["Età"] = GetString(rc, "age_band", "");
row["Premio"] = GetString(rc, "prize", "");
if (rc.TryGetProperty("runners", out var runnersEl) &&
runnersEl.ValueKind == JsonValueKind.Array)
{
row["N. Corridori"] = runnersEl.GetArrayLength();
}
else if (rc.TryGetProperty("field_size", out var fsEl) &&
fsEl.ValueKind == JsonValueKind.Number)
{
row["N. Corridori"] = fsEl.GetInt32();
}
else
{
row["N. Corridori"] = 0;
}
dt.Rows.Add(row);
}
catch
{
// Salta righe problematiche
}
}
}
}
catch
{
// Restituisci tabella vuota in caso di errore di parsing
}
return dt;
}
private DataTable ParseResultsResponse(string json)
{
var dt = CreateEmptyResultsTable();
if (string.IsNullOrEmpty(json)) return dt;
try
{
using (var doc = JsonDocument.Parse(json))
{
var root = doc.RootElement;
if (!root.TryGetProperty("results", out var resultsEl))
return dt;
foreach (var res in resultsEl.EnumerateArray())
{
try
{
var row = dt.NewRow();
row["Data"] = GetString(res, "date", "");
row["Ippodromo"] = GetString(res, "course", "");
row["Corsa"] = GetString(res, "race_name", "");
row["Distanza"] = GetString(res, "distance", "");
row["Terreno"] = GetString(res, "going", "");
if (res.TryGetProperty("runners", out var runnersEl) &&
runnersEl.ValueKind == JsonValueKind.Array)
{
int idx = 0;
foreach (var runner in runnersEl.EnumerateArray())
{
var pos = GetString(runner, "position", "");
if (pos == "1" || idx == 0)
{
row["1° Classificato"] = GetString(runner, "horse", "");
row["Fantino 1°"] = GetString(runner, "jockey", "");
row["SP 1°"] = GetString(runner, "sp", "");
}
else if (pos == "2" || idx == 1)
{
row["2° Classificato"] = GetString(runner, "horse", "");
}
else if (pos == "3" || idx == 2)
{
row["3° Classificato"] = GetString(runner, "horse", "");
}
idx++;
if (idx >= 3) break;
}
}
dt.Rows.Add(row);
}
catch
{
// Salta righe problematiche
}
}
}
}
catch
{
// Restituisci tabella vuota in caso di errore di parsing
}
return dt;
}
private static string GetString(JsonElement el, string property, string defaultValue)
{
if (el.TryGetProperty(property, out var prop) && prop.ValueKind == JsonValueKind.String)
return prop.GetString() ?? defaultValue;
if (el.TryGetProperty(property, out prop) && prop.ValueKind == JsonValueKind.Number)
return prop.ToString();
return defaultValue;
}
#endregion
}
}

View File

@@ -1,360 +1,583 @@
using System;
using System.Data;
using System.Windows.Forms;
using System.Drawing;
using BettingPredictor.UI.Controls;
using System.Windows.Forms;
using BettingPredictor.UI;
namespace BettingPredictor
{
partial class Main
{
// Container
private System.ComponentModel.IContainer components = null;
// Grafica moderna
private ModernTabControl tabControl;
// Layout
private Panel panelSidebar;
private Panel panelContent;
private Panel panelHeader;
private Label labelAppTitle;
private Label labelPageTitle;
// Horse tab
private TabPage tabPageHorse;
private ModernPanel panelHorseTop;
private ModernTextBox textBoxFolderPath;
private ModernButton buttonBrowse;
private ModernButton buttonPredict;
private ModernButton buttonImport;
private ModernProgressBar progressBarHorse;
private ModernLabel labelStatusHorse;
private ModernDataGridView dataGridViewHorse;
// Nav
private NavButton navFootball;
private NavButton navHorseRacing;
private NavButton navSettings;
private NavButton navInfo;
// Football tab
private TabPage tabPageFootball;
private ModernPanel panelFootballTop;
private ModernDateTimePicker dateTimePicker;
private ModernButton buttonImportFootball;
private ModernButton buttonDownloadFootball;
private ModernDataGridView dataGridViewFootball;
// Pagine
private Panel pageFootball;
private Panel pageHorseRacing;
private Panel pageSettings;
private Panel pageInfo;
// Football
private DateTimePicker dateTimePicker;
private ModernButton btnDownload;
private ModernButton btnExportCsv;
private ModernProgressBar progressBarFootball;
private ModernLabel labelStatusFootball;
private Label labelStatusFootball;
private DataGridView dataGridViewFootball;
// Horse Racing
private ComboBox cmbRacingDay;
private ModernButton btnDownloadRacing;
private ModernButton btnExportRacingCsv;
private ModernProgressBar progressBarRacing;
private Label labelStatusRacing;
private DataGridView dataGridViewRacing;
// Impostazioni
private Label lblApiKey;
private TextBox txtApiKey;
private Label lblExportPath;
private TextBox txtExportPath;
private ModernButton btnBrowseExport;
private Label lblRacingUser;
private TextBox txtRacingUser;
private Label lblRacingPass;
private TextBox txtRacingPass;
private ModernButton btnSaveSettings;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
// Form settings
this.SuspendLayout();
// Inizializza i componenti
InitializeTabControl();
InitializeHorseTab();
InitializeFootballTab();
//
// Main Form
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1400, 800);
this.BackColor = ModernTheme.Colors.PrimaryBackground;
this.Controls.Add(this.tabControl);
this.Font = ModernTheme.Fonts.RegularFont;
// ============================================================
// FORM
// ============================================================
this.ClientSize = new Size(1050, 620);
this.MinimumSize = new Size(860, 520);
this.Text = "Betting Predictor";
this.Name = "Main";
this.Text = "Betting Predictor - Modern UI";
this.StartPosition = FormStartPosition.CenterScreen;
this.Load += new System.EventHandler(this.Main_Load);
this.BackColor = ModernTheme.ContentBackground;
this.ForeColor = ModernTheme.TextPrimary;
this.Font = ModernTheme.BodyFont;
this.DoubleBuffered = true;
this.Load += new EventHandler(this.Main_Load);
this.Resize += new EventHandler(this.Main_Resize);
// ============================================================
// SIDEBAR (Dock=Left, larghezza fissa)
// ============================================================
this.panelSidebar = new Panel
{
Dock = DockStyle.Left,
Width = ModernTheme.SidebarWidth,
BackColor = ModernTheme.SidebarBackground
};
this.labelAppTitle = new Label
{
Text = "Betting\nPredictor",
Font = new Font("Segoe UI", 15F, FontStyle.Bold),
ForeColor = ModernTheme.PrimaryColor,
BackColor = ModernTheme.SidebarBackground,
Dock = DockStyle.Top,
Height = 72,
TextAlign = ContentAlignment.MiddleCenter
};
var sep = new Panel
{
Dock = DockStyle.Top,
Height = 1,
BackColor = ModernTheme.CardBorder
};
// Nav — ordine di aggiunta inverso rispetto a quello visivo (Dock=Top)
this.navInfo = new NavButton("Info", "i");
this.navInfo.Click += new EventHandler(this.navInfo_Click);
this.navSettings = new NavButton("Impostazioni", "#");
this.navSettings.Click += new EventHandler(this.navSettings_Click);
this.navHorseRacing = new NavButton("Corse Cavalli", "H");
this.navHorseRacing.Click += new EventHandler(this.navHorseRacing_Click);
this.navFootball = new NavButton("Calcio", "F");
this.navFootball.IsActive = true;
this.navFootball.Click += new EventHandler(this.navFootball_Click);
this.panelSidebar.Controls.Add(this.navInfo);
this.panelSidebar.Controls.Add(this.navSettings);
this.panelSidebar.Controls.Add(this.navHorseRacing);
this.panelSidebar.Controls.Add(this.navFootball);
this.panelSidebar.Controls.Add(sep);
this.panelSidebar.Controls.Add(this.labelAppTitle);
// ============================================================
// HEADER (Dock=Top dentro panelContent)
// ============================================================
this.panelHeader = new Panel
{
Dock = DockStyle.Top,
Height = 56,
BackColor = ModernTheme.HeaderBackground,
Padding = new Padding(24, 0, 24, 0)
};
this.labelPageTitle = new Label
{
Text = "Calcio",
Font = ModernTheme.TitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.HeaderBackground,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
this.panelHeader.Controls.Add(this.labelPageTitle);
var headerLine = new Panel
{
Dock = DockStyle.Bottom,
Height = 1,
BackColor = ModernTheme.CardBorder
};
this.panelHeader.Controls.Add(headerLine);
// ============================================================
// CONTENT WRAPPER (Dock=Fill, contiene header + pagine)
// ============================================================
this.panelContent = new Panel
{
Dock = DockStyle.Fill,
BackColor = ModernTheme.ContentBackground
};
// ============================================================
// PAGE: CALCIO
// ============================================================
this.pageFootball = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(24, 16, 24, 16),
BackColor = ModernTheme.ContentBackground,
Visible = true
};
// -- toolbar (altezza fissa, posizionamento manuale) --
var toolbarFb = new Panel
{
Dock = DockStyle.Top,
Height = 48,
BackColor = ModernTheme.ContentBackground
};
this.dateTimePicker = new DateTimePicker
{
Format = DateTimePickerFormat.Short,
Value = DateTime.Today,
Font = ModernTheme.BodyFont,
CalendarMonthBackground = ModernTheme.InputBackground,
CalendarForeColor = ModernTheme.InputText
};
this.btnDownload = new ModernButton { Text = "Scarica Partite", Size = new Size(140, 34) };
this.btnDownload.Click += new EventHandler(this.btnDownload_Click);
this.btnExportCsv = new ModernButton
{
Text = "Esporta CSV",
AccentColor = ModernTheme.SuccessColor,
Size = new Size(120, 34),
Enabled = false
};
this.btnExportCsv.Click += new EventHandler(this.btnExportCsv_Click);
toolbarFb.Controls.Add(this.dateTimePicker);
toolbarFb.Controls.Add(this.btnDownload);
toolbarFb.Controls.Add(this.btnExportCsv);
// -- status --
var statusFb = new Panel
{
Dock = DockStyle.Top,
Height = 30,
BackColor = ModernTheme.ContentBackground
};
this.progressBarFootball = new ModernProgressBar { Dock = DockStyle.Top };
this.labelStatusFootball = new Label
{
Text = "Pronto",
Font = ModernTheme.SmallFont,
ForeColor = ModernTheme.TextSecondary,
BackColor = ModernTheme.ContentBackground,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
statusFb.Controls.Add(this.labelStatusFootball);
statusFb.Controls.Add(this.progressBarFootball);
// -- grid --
this.dataGridViewFootball = new DataGridView { Dock = DockStyle.Fill };
((System.ComponentModel.ISupportInitialize)this.dataGridViewFootball).BeginInit();
ModernTheme.StyleDataGridView(this.dataGridViewFootball);
this.pageFootball.Controls.Add(this.dataGridViewFootball);
this.pageFootball.Controls.Add(statusFb);
this.pageFootball.Controls.Add(toolbarFb);
// ============================================================
// PAGE: CORSE CAVALLI
// ============================================================
this.pageHorseRacing = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(24, 16, 24, 16),
BackColor = ModernTheme.ContentBackground,
Visible = false
};
// -- toolbar racing --
var toolbarRacing = new Panel
{
Dock = DockStyle.Top,
Height = 48,
BackColor = ModernTheme.ContentBackground,
Name = "toolbarRacing"
};
this.cmbRacingDay = new ComboBox
{
DropDownStyle = ComboBoxStyle.DropDownList,
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
FlatStyle = FlatStyle.Flat,
Size = new Size(160, 28)
};
this.cmbRacingDay.Items.AddRange(new object[] { "Oggi", "Domani" });
this.cmbRacingDay.SelectedIndex = 0;
this.btnDownloadRacing = new ModernButton { Text = "Scarica Corse", Size = new Size(140, 34) };
this.btnDownloadRacing.Click += new EventHandler(this.btnDownloadRacing_Click);
this.btnExportRacingCsv = new ModernButton
{
Text = "Esporta CSV",
AccentColor = ModernTheme.SuccessColor,
Size = new Size(120, 34),
Enabled = false
};
this.btnExportRacingCsv.Click += new EventHandler(this.btnExportRacingCsv_Click);
toolbarRacing.Controls.Add(this.cmbRacingDay);
toolbarRacing.Controls.Add(this.btnDownloadRacing);
toolbarRacing.Controls.Add(this.btnExportRacingCsv);
// -- status racing --
var statusRacing = new Panel
{
Dock = DockStyle.Top,
Height = 30,
BackColor = ModernTheme.ContentBackground
};
this.progressBarRacing = new ModernProgressBar { Dock = DockStyle.Top };
this.labelStatusRacing = new Label
{
Text = "Pronto",
Font = ModernTheme.SmallFont,
ForeColor = ModernTheme.TextSecondary,
BackColor = ModernTheme.ContentBackground,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
statusRacing.Controls.Add(this.labelStatusRacing);
statusRacing.Controls.Add(this.progressBarRacing);
// -- grid racing --
this.dataGridViewRacing = new DataGridView { Dock = DockStyle.Fill };
((System.ComponentModel.ISupportInitialize)this.dataGridViewRacing).BeginInit();
ModernTheme.StyleDataGridView(this.dataGridViewRacing);
this.pageHorseRacing.Controls.Add(this.dataGridViewRacing);
this.pageHorseRacing.Controls.Add(statusRacing);
this.pageHorseRacing.Controls.Add(toolbarRacing);
// ============================================================
// PAGE: IMPOSTAZIONI
// ============================================================
this.pageSettings = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(24, 16, 24, 16),
BackColor = ModernTheme.ContentBackground,
Visible = false
};
var settingsInner = new Panel
{
Dock = DockStyle.Top,
Height = 400,
BackColor = ModernTheme.CardBackground,
Padding = new Padding(24)
};
this.lblApiKey = new Label
{
Text = "API Key (api-football)",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(24, 24)
};
this.txtApiKey = new TextBox
{
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(24, 52),
Size = new Size(500, 28)
};
this.lblExportPath = new Label
{
Text = "Cartella esportazione CSV",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(24, 100)
};
this.txtExportPath = new TextBox
{
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
BorderStyle = BorderStyle.FixedSingle,
ReadOnly = true,
Location = new Point(24, 128),
Size = new Size(400, 28)
};
this.btnBrowseExport = new ModernButton
{
Text = "Sfoglia...",
IsPrimary = false,
Size = new Size(90, 30),
Location = new Point(434, 126)
};
this.btnBrowseExport.Click += new EventHandler(this.btnBrowseExport_Click);
// Racing API credentials
this.lblRacingUser = new Label
{
Text = "Racing API — Username",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(24, 176)
};
this.txtRacingUser = new TextBox
{
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(24, 204),
Size = new Size(500, 28)
};
this.lblRacingPass = new Label
{
Text = "Racing API — Password",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(24, 248)
};
this.txtRacingPass = new TextBox
{
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
BorderStyle = BorderStyle.FixedSingle,
UseSystemPasswordChar = true,
Location = new Point(24, 276),
Size = new Size(500, 28)
};
this.btnSaveSettings = new ModernButton
{
Text = "Salva impostazioni",
AccentColor = ModernTheme.SuccessColor,
Size = new Size(170, 36),
Location = new Point(24, 330)
};
this.btnSaveSettings.Click += new EventHandler(this.btnSaveSettings_Click);
settingsInner.Controls.Add(this.btnSaveSettings);
settingsInner.Controls.Add(this.txtRacingPass);
settingsInner.Controls.Add(this.lblRacingPass);
settingsInner.Controls.Add(this.txtRacingUser);
settingsInner.Controls.Add(this.lblRacingUser);
settingsInner.Controls.Add(this.btnBrowseExport);
settingsInner.Controls.Add(this.txtExportPath);
settingsInner.Controls.Add(this.lblExportPath);
settingsInner.Controls.Add(this.txtApiKey);
settingsInner.Controls.Add(this.lblApiKey);
this.pageSettings.Controls.Add(settingsInner);
// ============================================================
// PAGE: INFO
// ============================================================
this.pageInfo = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(24, 16, 24, 16),
BackColor = ModernTheme.ContentBackground,
Visible = false
};
var infoInner = new Panel
{
Dock = DockStyle.Top,
Height = 280,
BackColor = ModernTheme.CardBackground,
Padding = new Padding(32)
};
int infoY = 32;
var lblInfoTitle = new Label
{
Text = "Betting Predictor",
Font = new Font("Segoe UI", 20F, FontStyle.Bold),
ForeColor = ModernTheme.PrimaryColor,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(32, infoY)
};
infoY += 48;
var lblVersion = new Label
{
Text = "Versione 1.0.0",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextSecondary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(32, infoY)
};
infoY += 36;
var lblDesc = new Label
{
Text = "Applicazione per lo scaricamento e l'analisi di dati sportivi\n" +
"tramite API esterne. Supporta l'esportazione in CSV.\n\n" +
"Sviluppato con .NET Framework 4.8.1 | WinForms",
Font = ModernTheme.BodyFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(32, infoY)
};
infoY += 90;
var lblCopy = new Label
{
Text = $"© {DateTime.Now.Year} — Tutti i diritti riservati",
Font = ModernTheme.SmallFont,
ForeColor = ModernTheme.TextSecondary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(32, infoY)
};
infoInner.Controls.Add(lblCopy);
infoInner.Controls.Add(lblDesc);
infoInner.Controls.Add(lblVersion);
infoInner.Controls.Add(lblInfoTitle);
this.pageInfo.Controls.Add(infoInner);
// ============================================================
// ASSEMBLY
// ============================================================
// Le pagine vanno aggiunte PRIMA dell'header nel panelContent
// perché con Dock=Fill devono occupare lo spazio rimanente.
this.panelContent.Controls.Add(this.pageFootball);
this.panelContent.Controls.Add(this.pageHorseRacing);
this.panelContent.Controls.Add(this.pageSettings);
this.panelContent.Controls.Add(this.pageInfo);
this.panelContent.Controls.Add(this.panelHeader);
this.Controls.Add(this.panelContent);
this.Controls.Add(this.panelSidebar);
((System.ComponentModel.ISupportInitialize)this.dataGridViewFootball).EndInit();
((System.ComponentModel.ISupportInitialize)this.dataGridViewRacing).EndInit();
this.ResumeLayout(false);
}
private void InitializeTabControl()
/// <summary>
/// Posiziona manualmente i controlli nelle toolbar
/// </summary>
private void LayoutToolbars()
{
this.tabControl = new ModernTabControl();
this.tabPageHorse = new TabPage();
this.tabPageFootball = new TabPage();
// Football toolbar
if (dateTimePicker != null && dateTimePicker.Parent != null)
{
var tb = dateTimePicker.Parent;
int w = tb.ClientSize.Width;
int y = 7;
//
// tabControl
//
this.tabControl.Controls.Add(this.tabPageHorse);
this.tabControl.Controls.Add(this.tabPageFootball);
this.tabControl.Dock = DockStyle.Fill;
this.tabControl.Location = new Point(0, 0);
this.tabControl.Name = "tabControl";
this.tabControl.SelectedIndex = 0;
this.tabControl.Size = new Size(1400, 800);
this.tabControl.TabIndex = 0;
dateTimePicker.SetBounds(0, y, 200, 28);
btnExportCsv.Location = new Point(w - btnExportCsv.Width, y);
btnDownload.Location = new Point(btnExportCsv.Left - btnDownload.Width - 10, y);
}
private void InitializeHorseTab()
// Horse Racing toolbar
if (cmbRacingDay != null && cmbRacingDay.Parent != null)
{
// Inizializza i controlli
this.panelHorseTop = new ModernPanel();
this.textBoxFolderPath = new ModernTextBox();
this.buttonBrowse = new ModernButton();
this.buttonImport = new ModernButton();
this.buttonPredict = new ModernButton();
this.progressBarHorse = new ModernProgressBar();
this.labelStatusHorse = new ModernLabel();
this.dataGridViewHorse = new ModernDataGridView();
var tb = cmbRacingDay.Parent;
int w = tb.ClientSize.Width;
int y = 7;
this.tabPageHorse.SuspendLayout();
this.panelHorseTop.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewHorse)).BeginInit();
//
// tabPageHorse
//
this.tabPageHorse.BackColor = ModernTheme.Colors.PrimaryBackground;
this.tabPageHorse.Controls.Add(this.dataGridViewHorse);
this.tabPageHorse.Controls.Add(this.labelStatusHorse);
this.tabPageHorse.Controls.Add(this.progressBarHorse);
this.tabPageHorse.Controls.Add(this.panelHorseTop);
this.tabPageHorse.Location = new Point(4, 44);
this.tabPageHorse.Name = "tabPageHorse";
this.tabPageHorse.Padding = new Padding(ModernTheme.Spacing.Large);
this.tabPageHorse.Size = new Size(1392, 752);
this.tabPageHorse.TabIndex = 0;
this.tabPageHorse.Text = "🏇 Horse Racing";
//
// panelHorseTop
//
this.panelHorseTop.BackColor = ModernTheme.Colors.SecondaryBackground;
this.panelHorseTop.BorderRadius = ModernTheme.BorderRadius.Large;
this.panelHorseTop.BorderColor = ModernTheme.Colors.BorderPrimary;
this.panelHorseTop.BorderWidth = 1;
this.panelHorseTop.Controls.Add(this.textBoxFolderPath);
this.panelHorseTop.Controls.Add(this.buttonBrowse);
this.panelHorseTop.Controls.Add(this.buttonImport);
this.panelHorseTop.Controls.Add(this.buttonPredict);
this.panelHorseTop.Dock = DockStyle.Top;
this.panelHorseTop.Location = new Point(15, 15);
this.panelHorseTop.Name = "panelHorseTop";
this.panelHorseTop.Padding = new Padding(ModernTheme.Spacing.Medium);
this.panelHorseTop.Size = new Size(1362, 60);
this.panelHorseTop.TabIndex = 0;
//
// textBoxFolderPath
//
this.textBoxFolderPath.Anchor = ((AnchorStyles)(((AnchorStyles.Top | AnchorStyles.Left) | AnchorStyles.Right)));
this.textBoxFolderPath.Location = new Point(10, 15);
this.textBoxFolderPath.Name = "textBoxFolderPath";
this.textBoxFolderPath.ReadOnly = true;
this.textBoxFolderPath.Size = new Size(920, 30);
this.textBoxFolderPath.TabIndex = 0;
this.textBoxFolderPath.Text = "Seleziona una cartella...";
this.textBoxFolderPath.ForeColor = ModernTheme.Colors.TextSecondary;
//
// buttonBrowse
//
this.buttonBrowse.Anchor = ((AnchorStyles)((AnchorStyles.Top | AnchorStyles.Right)));
this.buttonBrowse.Location = new Point(940, 15);
this.buttonBrowse.Name = "buttonBrowse";
this.buttonBrowse.Size = new Size(120, 30);
this.buttonBrowse.TabIndex = 1;
this.buttonBrowse.Text = "📁 Sfoglia";
this.buttonBrowse.NormalColor = ModernTheme.Colors.AccentPrimary;
this.buttonBrowse.HoverColor = ModernTheme.Colors.AccentSecondary;
this.buttonBrowse.Click += new EventHandler(this.buttonBrowse_Click);
//
// buttonImport
//
this.buttonImport.Anchor = ((AnchorStyles)((AnchorStyles.Top | AnchorStyles.Right)));
this.buttonImport.Location = new Point(1070, 15);
this.buttonImport.Name = "buttonImport";
this.buttonImport.Size = new Size(130, 30);
this.buttonImport.TabIndex = 2;
this.buttonImport.Text = "📥 Importa";
this.buttonImport.NormalColor = ModernTheme.Colors.AccentSuccess;
this.buttonImport.HoverColor = ColorTranslator.FromHtml("#6FE9D0");
this.buttonImport.Enabled = false;
this.buttonImport.Click += new EventHandler(this.buttonImport_Click);
//
// buttonPredict
//
this.buttonPredict.Anchor = ((AnchorStyles)((AnchorStyles.Top | AnchorStyles.Right)));
this.buttonPredict.Location = new Point(1210, 15);
this.buttonPredict.Name = "buttonPredict";
this.buttonPredict.Size = new Size(130, 30);
this.buttonPredict.TabIndex = 3;
this.buttonPredict.Text = "🎯 Predici";
this.buttonPredict.NormalColor = ModernTheme.Colors.AccentWarning;
this.buttonPredict.HoverColor = ColorTranslator.FromHtml("#E8B198");
this.buttonPredict.Click += new EventHandler(this.buttonPredict_Click);
//
// progressBarHorse
//
this.progressBarHorse.Anchor = ((AnchorStyles)(((AnchorStyles.Top | AnchorStyles.Left) | AnchorStyles.Right)));
this.progressBarHorse.Location = new Point(15, 85);
this.progressBarHorse.Name = "progressBarHorse";
this.progressBarHorse.Size = new Size(1362, 6);
this.progressBarHorse.TabIndex = 1;
//
// labelStatusHorse
//
this.labelStatusHorse.AutoSize = true;
this.labelStatusHorse.ForeColor = ModernTheme.Colors.TextSecondary;
this.labelStatusHorse.Font = ModernTheme.Fonts.RegularFont;
this.labelStatusHorse.Location = new Point(15, 100);
this.labelStatusHorse.Name = "labelStatusHorse";
this.labelStatusHorse.Size = new Size(38, 13);
this.labelStatusHorse.TabIndex = 2;
this.labelStatusHorse.Text = "Pronto";
this.labelStatusHorse.BackColor = Color.Transparent;
//
// dataGridViewHorse
//
this.dataGridViewHorse.Anchor = ((AnchorStyles)((((AnchorStyles.Top | AnchorStyles.Bottom) | AnchorStyles.Left) | AnchorStyles.Right)));
this.dataGridViewHorse.Location = new Point(15, 125);
this.dataGridViewHorse.Name = "dataGridViewHorse";
this.dataGridViewHorse.Size = new Size(1362, 612);
this.dataGridViewHorse.TabIndex = 3;
this.tabPageHorse.ResumeLayout(false);
this.tabPageHorse.PerformLayout();
this.panelHorseTop.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.dataGridViewHorse)).EndInit();
cmbRacingDay.SetBounds(0, y, 160, 28);
btnExportRacingCsv.Location = new Point(w - btnExportRacingCsv.Width, y);
btnDownloadRacing.Location = new Point(btnExportRacingCsv.Left - btnDownloadRacing.Width - 10, y);
}
private void InitializeFootballTab()
{
// Inizializza i controlli
this.panelFootballTop = new ModernPanel();
this.dateTimePicker = new ModernDateTimePicker();
this.buttonDownloadFootball = new ModernButton();
this.buttonImportFootball = new ModernButton();
this.progressBarFootball = new ModernProgressBar();
this.labelStatusFootball = new ModernLabel();
this.dataGridViewFootball = new ModernDataGridView();
this.tabPageFootball.SuspendLayout();
this.panelFootballTop.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewFootball)).BeginInit();
//
// tabPageFootball
//
this.tabPageFootball.BackColor = ModernTheme.Colors.PrimaryBackground;
this.tabPageFootball.Controls.Add(this.dataGridViewFootball);
this.tabPageFootball.Controls.Add(this.labelStatusFootball);
this.tabPageFootball.Controls.Add(this.progressBarFootball);
this.tabPageFootball.Controls.Add(this.panelFootballTop);
this.tabPageFootball.Location = new Point(4, 44);
this.tabPageFootball.Name = "tabPageFootball";
this.tabPageFootball.Padding = new Padding(ModernTheme.Spacing.Large);
this.tabPageFootball.Size = new Size(1392, 752);
this.tabPageFootball.TabIndex = 1;
this.tabPageFootball.Text = "⚽ Football";
//
// panelFootballTop
//
this.panelFootballTop.BackColor = ModernTheme.Colors.SecondaryBackground;
this.panelFootballTop.BorderRadius = ModernTheme.BorderRadius.Large;
this.panelFootballTop.BorderColor = ModernTheme.Colors.BorderPrimary;
this.panelFootballTop.BorderWidth = 1;
this.panelFootballTop.Controls.Add(this.dateTimePicker);
this.panelFootballTop.Controls.Add(this.buttonDownloadFootball);
this.panelFootballTop.Controls.Add(this.buttonImportFootball);
this.panelFootballTop.Dock = DockStyle.Top;
this.panelFootballTop.Location = new Point(15, 15);
this.panelFootballTop.Name = "panelFootballTop";
this.panelFootballTop.Padding = new Padding(ModernTheme.Spacing.Medium);
this.panelFootballTop.Size = new Size(1362, 60);
this.panelFootballTop.TabIndex = 0;
//
// dateTimePicker
//
this.dateTimePicker.Anchor = ((AnchorStyles)(((AnchorStyles.Top | AnchorStyles.Left) | AnchorStyles.Right)));
this.dateTimePicker.CalendarMonthBackground = ModernTheme.Colors.TertiaryBackground;
this.dateTimePicker.CalendarForeColor = ModernTheme.Colors.TextPrimary;
this.dateTimePicker.CalendarTitleBackColor = ModernTheme.Colors.SecondaryBackground;
this.dateTimePicker.CalendarTitleForeColor = ModernTheme.Colors.TextPrimary;
this.dateTimePicker.CalendarTrailingForeColor = ModernTheme.Colors.TextDisabled;
this.dateTimePicker.Font = ModernTheme.Fonts.RegularFont;
this.dateTimePicker.Location = new Point(10, 18);
this.dateTimePicker.Name = "dateTimePicker";
this.dateTimePicker.Size = new Size(1090, 25);
this.dateTimePicker.TabIndex = 0;
this.dateTimePicker.Value = DateTime.Today;
//
// buttonDownloadFootball
//
this.buttonDownloadFootball.Anchor = ((AnchorStyles)((AnchorStyles.Top | AnchorStyles.Right)));
this.buttonDownloadFootball.Location = new Point(1110, 15);
this.buttonDownloadFootball.Name = "buttonDownloadFootball";
this.buttonDownloadFootball.Size = new Size(120, 30);
this.buttonDownloadFootball.TabIndex = 1;
this.buttonDownloadFootball.Text = "📥 Scarica";
this.buttonDownloadFootball.NormalColor = ModernTheme.Colors.AccentPrimary;
this.buttonDownloadFootball.HoverColor = ModernTheme.Colors.AccentSecondary;
this.buttonDownloadFootball.Click += new EventHandler(this.buttonDownloadFootball_Click);
//
// buttonImportFootball
//
this.buttonImportFootball.Anchor = ((AnchorStyles)((AnchorStyles.Top | AnchorStyles.Right)));
this.buttonImportFootball.Location = new Point(1240, 15);
this.buttonImportFootball.Name = "buttonImportFootball";
this.buttonImportFootball.Size = new Size(110, 30);
this.buttonImportFootball.TabIndex = 2;
this.buttonImportFootball.Text = "🔄 Importa";
this.buttonImportFootball.NormalColor = ModernTheme.Colors.AccentSuccess;
this.buttonImportFootball.HoverColor = ColorTranslator.FromHtml("#6FE9D0");
this.buttonImportFootball.Click += new EventHandler(this.buttonImportFootball_Click);
//
// progressBarFootball
//
this.progressBarFootball.Anchor = ((AnchorStyles)(((AnchorStyles.Top | AnchorStyles.Left) | AnchorStyles.Right)));
this.progressBarFootball.Location = new Point(15, 85);
this.progressBarFootball.Name = "progressBarFootball";
this.progressBarFootball.Size = new Size(1362, 6);
this.progressBarFootball.TabIndex = 1;
//
// labelStatusFootball
//
this.labelStatusFootball.AutoSize = true;
this.labelStatusFootball.ForeColor = ModernTheme.Colors.TextSecondary;
this.labelStatusFootball.Font = ModernTheme.Fonts.RegularFont;
this.labelStatusFootball.Location = new Point(15, 100);
this.labelStatusFootball.Name = "labelStatusFootball";
this.labelStatusFootball.Size = new Size(38, 13);
this.labelStatusFootball.TabIndex = 2;
this.labelStatusFootball.Text = "Pronto";
this.labelStatusFootball.BackColor = Color.Transparent;
//
// dataGridViewFootball
//
this.dataGridViewFootball.Anchor = ((AnchorStyles)((((AnchorStyles.Top | AnchorStyles.Bottom) | AnchorStyles.Left) | AnchorStyles.Right)));
this.dataGridViewFootball.Location = new Point(15, 125);
this.dataGridViewFootball.Name = "dataGridViewFootball";
this.dataGridViewFootball.Size = new Size(1362, 612);
this.dataGridViewFootball.TabIndex = 3;
this.tabPageFootball.ResumeLayout(false);
this.tabPageFootball.PerformLayout();
this.panelFootballTop.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.dataGridViewFootball)).EndInit();
}
}
}

View File

@@ -1,14 +1,8 @@
using HorseRacingPredictor.Football;
using HorseRacingPredictor.Football.Manager;
using HorseRacingPredictor.Horses;
using HorseRacingPredictor.Horses.ML;
using BettingPredictor.UI;
using BettingPredictor.UI.Controls;
using BettingPredictor.UI;
using System;
using System.Data;
using System.IO;
using System.Linq;
using System.Threading;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
@@ -16,682 +10,385 @@ namespace BettingPredictor
{
public partial class Main : Form
{
private readonly HorseRacingPredictor.Horses.Calculator calculator;
private readonly HorseRacingPredictor.Horses.Database horseDbManager;
private readonly HorseRacingPredictor.Horses.FileReader fileReaderHorses;
private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
// Nuova classe centralizzata per l'API Football
private readonly HorseRacingPredictor.Football.Main footballManager;
private HorseRacingPredictor.HorseRacing.Main racingManager;
private DataTable footballData;
private DataTable racingData;
private readonly MachineLearningService mlService = new MachineLearningService();
// Credenziali predefinite Racing API
private const string DefaultRacingUser = "qi1mHOHPquDY9KNDASAeGipy";
private const string DefaultRacingPass = "RXNFU1YX27R9rTnk8Vop8ZfH";
// Memorizza i dati importati per utilizzarli nella fase di predizione
private DataTable importedHorsesData;
// Pagine e nav gestiti come array per semplificare la navigazione
private Panel[] pages;
private NavButton[] navButtons;
private string[] pageTitles;
public Main()
{
InitializeComponent();
calculator = new HorseRacingPredictor.Horses.Calculator();
horseDbManager = new HorseRacingPredictor.Horses.Database();
fileReaderHorses = new HorseRacingPredictor.Horses.FileReader();
// Inizializza il gestore centralizzato del Football
footballManager = new HorseRacingPredictor.Football.Main();
racingManager = new HorseRacingPredictor.HorseRacing.Main(DefaultRacingUser, DefaultRacingPass);
// Disabilita il pulsante di importazione fino a quando non viene selezionato un percorso
buttonImport.Enabled = false;
// Il pulsante di predizione deve essere sempre abilitato
buttonPredict.Enabled = true;
pages = new[] { pageFootball, pageHorseRacing, pageSettings, pageInfo };
navButtons = new[] { navFootball, navHorseRacing, navSettings, navInfo };
pageTitles = new[] { "Calcio", "Corse Cavalli", "Impostazioni", "Informazioni" };
}
/// <summary>
/// Importa i dati dei file CSV delle corse dei cavalli
/// </summary>
private void ImportCsvFilesHorse(string folderPath)
#region Navigation
private void ShowPage(int index)
{
if (string.IsNullOrEmpty(folderPath))
for (int i = 0; i < pages.Length; i++)
{
MessageBox.Show("Seleziona un percorso valido.", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
pages[i].Visible = (i == index);
navButtons[i].IsActive = (i == index);
}
labelPageTitle.Text = pageTitles[index];
}
private void navFootball_Click(object sender, EventArgs e) => ShowPage(0);
private void navHorseRacing_Click(object sender, EventArgs e) => ShowPage(1);
private void navSettings_Click(object sender, EventArgs e) => ShowPage(2);
private void navInfo_Click(object sender, EventArgs e) => ShowPage(3);
#endregion
#region Football
private async Task DownloadFootballFixturesAsync(DateTime selectedDate)
{
try
{
// Usa il metodo FileReaderManagerHorses per ottenere i file CSV
var csvFiles = fileReaderHorses.GetHorseRaceFiles(folderPath);
if (csvFiles.Count == 0)
progressBarFootball.Minimum = 0;
progressBarFootball.Maximum = 100;
progressBarFootball.Value = 0;
labelStatusFootball.Text = "Scaricamento elenco partite...";
dateTimePicker.Enabled = false;
btnDownload.Enabled = false;
btnExportCsv.Enabled = false;
var progress = new Progress<int>(v => progressBarFootball.Value = v);
var status = new Progress<string>(s => labelStatusFootball.Text = s);
var table = await Task.Run(() =>
footballManager.GetTodayFixtures(selectedDate, progress, status));
footballData = table;
dataGridViewFootball.DataSource = footballData;
if (footballData != null && footballData.Rows.Count > 0)
{
MessageBox.Show("Nessun file CSV trovato nel percorso selezionato.", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Creiamo una tabella combinata per tutti i dati
DataTable combinedTable = null;
// Inizializza la progress bar
progressBarHorse.Minimum = 0;
progressBarHorse.Maximum = csvFiles.Count;
progressBarHorse.Value = 0;
labelStatusHorse.Text = "Inizializzazione importazione...";
// Utilizziamo Task per non bloccare l'UI
Task.Run(() =>
{
for (int i = 0; i < csvFiles.Count; i++)
{
var file = csvFiles[i];
// Aggiorna la UI con lo stato corrente
this.Invoke(new Action(() =>
{
progressBarHorse.Value = i + 1;
labelStatusHorse.Text = $"Importazione file {i + 1} di {csvFiles.Count}: {Path.GetFileName(file)}";
}));
try
{
// Leggi i dati dal file CSV
var horsesData = fileReaderHorses.ReadHorseDataFromFile(file);
// Al primo file, creiamo la tabella combinata
if (combinedTable == null)
{
// Creiamo una nuova tabella vuota
combinedTable = new DataTable();
// Aggiungiamo le colonne necessarie
combinedTable.Columns.Add("File", typeof(string));
combinedTable.Columns.Add("Data Corsa", typeof(DateTime));
combinedTable.Columns.Add("Meeting", typeof(string));
combinedTable.Columns.Add("Corsa", typeof(int));
combinedTable.Columns.Add("Numero", typeof(string));
combinedTable.Columns.Add("Nome Cavallo", typeof(string));
combinedTable.Columns.Add("Età", typeof(int));
combinedTable.Columns.Add("Genere", typeof(string));
combinedTable.Columns.Add("Peso", typeof(decimal));
combinedTable.Columns.Add("Quota", typeof(decimal));
combinedTable.Columns.Add("Risultato Reale", typeof(string));
// La colonna "Posizione Prevista" verrà aggiunta nella fase di predizione
}
// Inserisci i dati nel database
horseDbManager.ProcessAndInsertHorseRaceData(file);
// Estrai le informazioni sulla corsa dal nome del file
var raceInfo = fileReaderHorses.GetRaceInfoFromFileName(file);
// Aggiungi i dati dei cavalli alla tabella combinata (senza predizioni)
foreach (var horseData in horsesData)
{
DataRow newRow = combinedTable.NewRow();
newRow["File"] = Path.GetFileName(file);
newRow["Data Corsa"] = raceInfo.RaceDate;
newRow["Meeting"] = raceInfo.MeetingName;
newRow["Corsa"] = raceInfo.RaceNumber;
newRow["Numero"] = horseData.Num;
newRow["Nome Cavallo"] = horseData.HorseName;
newRow["Età"] = horseData.Age;
newRow["Genere"] = horseData.Gender;
newRow["Peso"] = horseData.WeightCarried;
newRow["Quota"] = horseData.BestFixedOdds;
newRow["Risultato Reale"] = horseData.FinishResult;
combinedTable.Rows.Add(newRow);
}
}
catch (Exception ex)
{
this.Invoke(new Action(() =>
{
MessageBox.Show($"Errore durante l'elaborazione del file {Path.GetFileName(file)}: {ex.Message}",
"Errore Elaborazione", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}));
// Continua con il prossimo file
}
}
// Completamento dell'operazione
this.Invoke(new Action(() =>
{
if (combinedTable != null && combinedTable.Rows.Count > 0)
{
// Salva i dati importati nella variabile di classe
importedHorsesData = combinedTable;
// Mostra i dati nella DataGridView
dataGridViewHorse.DataSource = importedHorsesData;
// Formatta la griglia
FormatHorseDataGrid(dataGridViewHorse);
labelStatusHorse.Text = $"Importazione completata: {combinedTable.Rows.Count} cavalli importati";
btnExportCsv.Enabled = true;
labelStatusFootball.Text = $"Scaricate {footballData.Rows.Count} partite";
}
else
{
labelStatusHorse.Text = "Nessun dato valido trovato nei file CSV";
labelStatusFootball.Text = "Nessuna partita trovata per la data selezionata";
}
// Riabilita il pulsante importa al termine dell'operazione
buttonImport.Enabled = true;
}));
});
}
catch (Exception ex)
{
MessageBox.Show($"Errore durante il caricamento dei file CSV: {ex.Message}",
MessageBox.Show($"Errore durante lo scaricamento:\n{ex.Message}",
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
labelStatusHorse.Text = "Errore durante l'importazione";
labelStatusFootball.Text = "Errore nello scaricamento";
progressBarFootball.Value = 0;
}
finally
{
dateTimePicker.Enabled = true;
btnDownload.Enabled = true;
}
}
private void ShowStrikeRateStats(DataTable table)
private void ExportFootballToCsv()
{
// Raggruppa per gara usando Data Corsa, Meeting, Corsa
var grouped = table.AsEnumerable()
.Where(row => row["Risultato Reale"] != DBNull.Value && row["Posizione Prevista"] != DBNull.Value)
.GroupBy(row => new {
DataCorsa = row["Data Corsa"],
Meeting = row["Meeting"].ToString(),
Corsa = row["Corsa"]
});
string stats = "Statistiche Strike Rate per Gara:\n";
int gareTotali = 0, winTotali = 0, placeTotali = 0;
foreach (var group in grouped)
if (footballData == null || footballData.Rows.Count == 0)
{
gareTotali++;
int tot = 0, winOk = 0, placeOk = 0;
foreach (var row in group)
{
if (int.TryParse(row["Risultato Reale"].ToString(), out int real) &&
float.TryParse(row["Posizione Prevista"].ToString(), out float pred))
{
tot++;
if (real == 1 && pred <= 1.5f) winOk++;
if (real <= 3 && pred <= 3.5f) placeOk++;
}
}
winTotali += winOk;
placeTotali += placeOk;
stats += $"Gara: {group.Key.DataCorsa:dd/MM/yyyy} - {group.Key.Meeting} - N°{group.Key.Corsa}\n" +
$" Cavalli: {tot}\n" +
$" Vincitore previsto corretto: {winOk} ({(winOk * 100.0 / tot):F1}%)\n" +
$" Piazzati previsti corretti: {placeOk} ({(placeOk * 100.0 / tot):F1}%)\n\n";
}
if (gareTotali > 0)
{
stats += $"Totale gare: {gareTotali}\n" +
$"Totale vincitori previsti corretti: {winTotali}\n" +
$"Totale piazzati previsti corretti: {placeTotali}\n";
MessageBox.Show(stats, "Strike Rate per Gara", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
/// <summary>
/// Formatta la griglia dei dati dei cavalli per una migliore visualizzazione
/// </summary>
private void FormatHorseDataGrid(ModernDataGridView grid)
{
if (grid.Columns.Count == 0)
MessageBox.Show("Nessun dato da esportare. Scarica prima le partite.",
"Nessun dato", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
// Nascondi le colonne Età, Genere e Peso
foreach (var colName in new[] { "Età", "Genere", "Peso" })
{
if (grid.Columns.Contains(colName))
grid.Columns[colName].Visible = false;
}
// Imposta l'ordine delle colonne principali
if (grid.Columns.Contains("File"))
grid.Columns["File"].DisplayIndex = 0;
if (grid.Columns.Contains("Data Corsa"))
grid.Columns["Data Corsa"].DisplayIndex = 1;
if (grid.Columns.Contains("Meeting"))
grid.Columns["Meeting"].DisplayIndex = 2;
if (grid.Columns.Contains("Corsa"))
grid.Columns["Corsa"].DisplayIndex = 3;
if (grid.Columns.Contains("Numero"))
grid.Columns["Numero"].DisplayIndex = 4;
if (grid.Columns.Contains("Nome Cavallo"))
grid.Columns["Nome Cavallo"].DisplayIndex = 5;
if (grid.Columns.Contains("Quota"))
grid.Columns["Quota"].DisplayIndex = 6;
if (grid.Columns.Contains("Risultato Reale"))
grid.Columns["Risultato Reale"].DisplayIndex = 7;
if (grid.Columns.Contains("Posizione Prevista"))
grid.Columns["Posizione Prevista"].DisplayIndex = 8;
string folder = txtExportPath.Text;
string filePath = null;
// Imposta il formato delle date
if (grid.Columns.Contains("Data Corsa"))
grid.Columns["Data Corsa"].DefaultCellStyle.Format = "dd/MM/yyyy";
// Imposta il formato numerico per le quote
if (grid.Columns.Contains("Quota"))
grid.Columns["Quota"].DefaultCellStyle.Format = "N2";
if (grid.Columns.Contains("Posizione Prevista"))
grid.Columns["Posizione Prevista"].DefaultCellStyle.Format = "N1";
// Imposta l'allineamento al centro per alcune colonne
foreach (var columnName in new[] { "Numero", "Età", "Corsa", "Risultato Reale", "Posizione Prevista" })
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder))
{
if (grid.Columns.Contains(columnName))
grid.Columns[columnName].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
filePath = Path.Combine(folder, $"Partite_{dateTimePicker.Value:yyyy-MM-dd}.csv");
}
else
{
using (var dlg = new SaveFileDialog())
{
dlg.Filter = "File CSV|*.csv";
dlg.FileName = $"Partite_{dateTimePicker.Value:yyyy-MM-dd}.csv";
if (dlg.ShowDialog() != DialogResult.OK) return;
filePath = dlg.FileName;
}
}
// Usa il metodo della griglia moderna per evidenziare i vincitori
grid.HighlightWinnerRows();
// Abilita il riordino delle colonne
grid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
grid.AllowUserToOrderColumns = true;
}
// Add these methods to handle the download and import operations
/// <summary>
/// Nuovo metodo per gestire lo scaricamento dei dati calcistici
/// </summary>
private async Task DownloadFootballDataAsync(DateTime selectedDate)
{
try
{
// Inizializza e mostra la barra di avanzamento
progressBarFootball.Minimum = 0;
progressBarFootball.Maximum = 100;
progressBarFootball.Value = 0;
labelStatusFootball.Text = "Scaricamento delle partite in corso...";
var sb = new StringBuilder();
// Disabilita i controlli durante l'elaborazione
dateTimePicker.Enabled = false;
buttonDownloadFootball.Enabled = false;
buttonImportFootball.Enabled = false;
var headers = new string[footballData.Columns.Count];
for (int i = 0; i < footballData.Columns.Count; i++)
headers[i] = footballData.Columns[i].ColumnName;
sb.AppendLine(string.Join(";", headers));
// Crea gli oggetti progress che aggiornano la UI in modo thread-safe
var progressCallback = new Progress<int>(value =>
foreach (DataRow row in footballData.Rows)
{
progressBarFootball.Value = value;
});
var statusCallback = new Progress<string>(status =>
var vals = new string[footballData.Columns.Count];
for (int i = 0; i < footballData.Columns.Count; i++)
{
labelStatusFootball.Text = status;
});
var v = row[i]?.ToString() ?? "";
if (v.Contains(";") || v.Contains("\""))
v = "\"" + v.Replace("\"", "\"\"") + "\"";
vals[i] = v;
}
sb.AppendLine(string.Join(";", vals));
}
// Usa la nuova classe centralizzata del Football per scaricare i dati
await Task.Run(() =>
footballManager.DownloadFixturesAndOdds(selectedDate, progressCallback, statusCallback));
// Riabilita i controlli
dateTimePicker.Enabled = true;
buttonDownloadFootball.Enabled = true;
buttonImportFootball.Enabled = true;
labelStatusFootball.Text = "Scaricamento completato. Pronto per l'importazione.";
File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
labelStatusFootball.Text = $"CSV esportato: {Path.GetFileName(filePath)}";
MessageBox.Show($"Esportate {footballData.Rows.Count} partite in:\n{filePath}",
"Esportazione completata", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
// Gestione degli errori
MessageBox.Show($"Errore durante lo scaricamento dei dati calcistici: {ex.Message}",
MessageBox.Show($"Errore durante l'esportazione CSV:\n{ex.Message}",
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
// Riabilita i controlli anche in caso di errore
dateTimePicker.Enabled = true;
buttonDownloadFootball.Enabled = true;
buttonImportFootball.Enabled = true;
labelStatusFootball.Text = "Errore nello scaricamento dati";
progressBarFootball.Value = 0;
}
}
/// <summary>
/// Nuovo metodo per gestire l'importazione dei dati calcistici
/// </summary>
private async Task ImportFootballDataAsync(ModernDataGridView dataGridViewFootball)
#endregion
#region Horse Racing
private async Task DownloadRacecardsAsync()
{
try
{
// Inizializza e mostra la barra di avanzamento
progressBarFootball.Minimum = 0;
progressBarFootball.Maximum = 100;
progressBarFootball.Value = 0;
labelStatusFootball.Text = "Importazione dati dalle risposte API in corso...";
progressBarRacing.Minimum = 0;
progressBarRacing.Maximum = 100;
progressBarRacing.Value = 0;
labelStatusRacing.Text = "Scaricamento racecard...";
// Disabilita i controlli durante l'elaborazione
dateTimePicker.Enabled = false;
buttonDownloadFootball.Enabled = false;
buttonImportFootball.Enabled = false;
cmbRacingDay.Enabled = false;
btnDownloadRacing.Enabled = false;
btnExportRacingCsv.Enabled = false;
// Crea gli oggetti progress che aggiornano la UI in modo thread-safe
var progressCallback = new Progress<int>(value =>
var progress = new Progress<int>(v => progressBarRacing.Value = v);
var status = new Progress<string>(s => labelStatusRacing.Text = s);
string day = cmbRacingDay.SelectedIndex == 0 ? "today" : "tomorrow";
var table = await Task.Run(() =>
racingManager.GetRacecards(day, progress, status));
racingData = table;
dataGridViewRacing.DataSource = racingData;
if (racingData != null && racingData.Rows.Count > 0)
{
progressBarFootball.Value = value;
});
var statusCallback = new Progress<string>(status =>
btnExportRacingCsv.Enabled = true;
labelStatusRacing.Text = $"Trovate {racingData.Rows.Count} corse";
}
else
{
labelStatusFootball.Text = status;
});
// Usa la nuova classe centralizzata del Football per importare i dati
var footballDataTable = await Task.Run(() =>
footballManager.ImportFromApiResponses(progressCallback, statusCallback));
// Aggiorna la UI con i risultati (già thread-safe perché eseguito sul thread UI)
dataGridViewFootball.DataSource = footballDataTable;
// Formatta la griglia dati
FormatFootballDataGrid(dataGridViewFootball);
// Riabilita i controlli
dateTimePicker.Enabled = true;
buttonDownloadFootball.Enabled = true;
buttonImportFootball.Enabled = true;
labelStatusFootball.Text = $"Importati {footballDataTable?.Rows.Count ?? 0} eventi";
labelStatusRacing.Text = "Nessuna corsa trovata";
}
}
catch (Exception ex)
{
// Gestione degli errori
MessageBox.Show($"Errore durante l'importazione dei dati calcistici: {ex.Message}",
MessageBox.Show($"Errore durante lo scaricamento:\n{ex.Message}",
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
// Riabilita i controlli anche in caso di errore
dateTimePicker.Enabled = true;
buttonDownloadFootball.Enabled = true;
buttonImportFootball.Enabled = true;
labelStatusFootball.Text = "Errore nell'importazione dati";
progressBarFootball.Value = 0;
labelStatusRacing.Text = "Errore nello scaricamento";
progressBarRacing.Value = 0;
}
finally
{
cmbRacingDay.Enabled = true;
btnDownloadRacing.Enabled = true;
}
}
/// <summary>
/// Metodo aggiornato per il processo completo (scaricamento + importazione)
/// </summary>
private async Task ProcessFootballDataAsync(DateTime selectedDate, ModernDataGridView dataGridViewFootball)
private void ExportRacingToCsv()
{
try
if (racingData == null || racingData.Rows.Count == 0)
{
// Prima scarica, poi importa
await DownloadFootballDataAsync(selectedDate);
await ImportFootballDataAsync(dataGridViewFootball);
}
catch (Exception ex)
{
// Gestione degli errori
MessageBox.Show($"Errore durante l'elaborazione dei dati calcistici: {ex.Message}",
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
// Reset dello stato UI
dateTimePicker.Enabled = true;
buttonDownloadFootball.Enabled = true;
buttonImportFootball.Enabled = true;
labelStatusFootball.Text = "Errore nell'elaborazione dati";
progressBarFootball.Value = 0;
}
}
/// <summary>
/// Formatta la griglia dei dati calcistici per una migliore visualizzazione
/// </summary>
private void FormatFootballDataGrid(ModernDataGridView grid)
{
if (grid.Columns.Count == 0)
MessageBox.Show("Nessun dato da esportare. Scarica prima le corse.",
"Nessun dato", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// Imposta l'ordine delle colonne principali
if (grid.Columns.Contains("Data / Ora"))
grid.Columns["Data / Ora"].DisplayIndex = 0;
if (grid.Columns.Contains("Paese"))
grid.Columns["Paese"].DisplayIndex = 1;
if (grid.Columns.Contains("Campionato"))
grid.Columns["Campionato"].DisplayIndex = 2;
if (grid.Columns.Contains("Casa"))
grid.Columns["Casa"].DisplayIndex = 3;
if (grid.Columns.Contains("Trasferta"))
grid.Columns["Trasferta"].DisplayIndex = 4;
if (grid.Columns.Contains("Previsione"))
grid.Columns["Previsione"].DisplayIndex = 5;
string folder = txtExportPath.Text;
string dayLabel = cmbRacingDay.SelectedIndex == 0 ? "oggi" : "domani";
string filePath = null;
// Imposta il formato delle date
if (grid.Columns.Contains("Data / Ora"))
grid.Columns["Data / Ora"].DefaultCellStyle.Format = "dd/MM/yyyy HH:mm";
// Imposta il formato numerico per le quote
foreach (var quotaColumn in new[] { "Quota Casa", "Quota Pareggio", "Quota Trasferta" })
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder))
{
if (grid.Columns.Contains(quotaColumn))
grid.Columns[quotaColumn].DefaultCellStyle.Format = "N2";
filePath = Path.Combine(folder, $"Corse_{dayLabel}_{DateTime.Now:yyyy-MM-dd}.csv");
}
// Imposta l'allineamento al centro per alcune colonne
foreach (var columnName in new[] { "Goals Casa", "Goals Trasferta", "Risultato", "Stato" })
else
{
if (grid.Columns.Contains(columnName))
grid.Columns[columnName].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
}
// Abilita il riordino delle colonne
grid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
grid.AllowUserToOrderColumns = true;
}
private string BrowseFolder()
using (var dlg = new SaveFileDialog())
{
using (FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog())
dlg.Filter = "File CSV|*.csv";
dlg.FileName = $"Corse_{dayLabel}_{DateTime.Now:yyyy-MM-dd}.csv";
if (dlg.ShowDialog() != DialogResult.OK) return;
filePath = dlg.FileName;
}
}
try
{
folderBrowserDialog.Description = "Seleziona la cartella contenente i file CSV delle corse dei cavalli";
folderBrowserDialog.ShowNewFolderButton = false;
return folderBrowserDialog.ShowDialog() == DialogResult.OK ? folderBrowserDialog.SelectedPath : null;
}
}
var sb = new StringBuilder();
#region EventHandlers
var headers = new string[racingData.Columns.Count];
for (int i = 0; i < racingData.Columns.Count; i++)
headers[i] = racingData.Columns[i].ColumnName;
sb.AppendLine(string.Join(";", headers));
// Gestore evento per il caricamento dei dati dei cavalli - solo per importazione
private void buttonBrowse_Click(object sender, EventArgs e)
foreach (DataRow row in racingData.Rows)
{
// Seleziona cartella con file CSV
string folderPath = BrowseFolder();
if (!string.IsNullOrEmpty(folderPath))
var vals = new string[racingData.Columns.Count];
for (int i = 0; i < racingData.Columns.Count; i++)
{
// Imposta il percorso nel textbox
textBoxFolderPath.Text = folderPath;
// Abilita il pulsante di importazione
buttonImport.Enabled = true;
var v = row[i]?.ToString() ?? "";
if (v.Contains(";") || v.Contains("\""))
v = "\"" + v.Replace("\"", "\"\"") + "\"";
vals[i] = v;
}
sb.AppendLine(string.Join(";", vals));
}
// Replace buttonLoadFootball_Click with these two event handlers:
private void buttonDownloadFootball_Click(object sender, EventArgs e)
File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
labelStatusRacing.Text = $"CSV esportato: {Path.GetFileName(filePath)}";
MessageBox.Show($"Esportate {racingData.Rows.Count} corse in:\n{filePath}",
"Esportazione completata", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
// Usa direttamente la proprietà Value del DateTimePicker
DateTime selectedDate = dateTimePicker.Value;
_ = DownloadFootballDataAsync(selectedDate);
MessageBox.Show($"Errore durante l'esportazione CSV:\n{ex.Message}",
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// Gestore evento per il pulsante di importazione dati football
private void buttonImportFootball_Click(object sender, EventArgs e)
#endregion
#region Settings
private string SettingsFilePath =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.ini");
private void LoadSettings()
{
// Process and import data from frontier table to final tables
_ = ImportFootballDataAsync(dataGridViewFootball);
try
{
// Imposta valori predefiniti Racing API
txtRacingUser.Text = DefaultRacingUser;
txtRacingPass.Text = DefaultRacingPass;
if (!File.Exists(SettingsFilePath)) return;
foreach (var line in File.ReadAllLines(SettingsFilePath))
{
var idx = line.IndexOf('=');
if (idx < 0) continue;
var key = line.Substring(0, idx).Trim();
var val = line.Substring(idx + 1).Trim();
if (key == "ApiKey") txtApiKey.Text = val;
else if (key == "ExportPath") txtExportPath.Text = val;
else if (key == "RacingUser") txtRacingUser.Text = val;
else if (key == "RacingPass") txtRacingPass.Text = val;
}
// Nel metodo Main_Load, rimuovi il codice che popolava il ComboBox
// Aggiorna il manager con le credenziali caricate
racingManager.UpdateCredentials(txtRacingUser.Text, txtRacingPass.Text);
}
catch { }
}
private void SaveSettings()
{
try
{
var sb = new StringBuilder();
sb.AppendLine($"ApiKey={txtApiKey.Text.Trim()}");
sb.AppendLine($"ExportPath={txtExportPath.Text.Trim()}");
sb.AppendLine($"RacingUser={txtRacingUser.Text.Trim()}");
sb.AppendLine($"RacingPass={txtRacingPass.Text.Trim()}");
File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8);
// Aggiorna il manager con le nuove credenziali
racingManager.UpdateCredentials(txtRacingUser.Text.Trim(), txtRacingPass.Text.Trim());
MessageBox.Show("Impostazioni salvate con successo.",
"Salvato", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Errore nel salvataggio:\n{ex.Message}",
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
#region Event handlers
private void Main_Load(object sender, EventArgs e)
{
// Imposta la data di ieri per default
dateTimePicker.Value = DateTime.Today.AddDays(-1);
// Inizializza le barre di avanzamento
progressBarHorse.Value = 0;
dateTimePicker.Value = DateTime.Today;
progressBarFootball.Value = 0;
labelStatusHorse.Text = "Pronto";
labelStatusFootball.Text = "Pronto";
progressBarRacing.Value = 0;
labelStatusRacing.Text = "Pronto";
LoadSettings();
LayoutToolbars();
}
// Gestore dell'evento per il pulsante di generazione predizioni
private async void buttonPredict_Click(object sender, EventArgs e)
private void Main_Resize(object sender, EventArgs e)
{
// Inizializza e configura il servizio ML
string connectionString = "Server=DESKTOP-9O9JHFS;Database=TestBS_Horses;User Id=sa;Password=Asti2019;";
mlService.ConnectionString = connectionString;
// Verifica se esiste il modello ML o se deve essere addestrato
string modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Models", "HorseRaceModel.zip");
if (!File.Exists(modelPath))
{
try
{
labelStatusHorse.Text = "Addestramento modello ML in corso...";
await Task.Run(() => mlService.TrainModel(connectionString));
labelStatusHorse.Text = "Addestramento modello ML completato";
LayoutToolbars();
}
catch (Exception ex)
private void btnDownload_Click(object sender, EventArgs e)
{
MessageBox.Show("Errore durante l'addestramento del modello ML:\n" + ex.Message, "Errore ML", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
_ = DownloadFootballFixturesAsync(dateTimePicker.Value);
}
private void btnExportCsv_Click(object sender, EventArgs e)
{
ExportFootballToCsv();
}
private void btnDownloadRacing_Click(object sender, EventArgs e)
{
_ = DownloadRacecardsAsync();
}
private void btnExportRacingCsv_Click(object sender, EventArgs e)
{
ExportRacingToCsv();
}
private void btnBrowseExport_Click(object sender, EventArgs e)
{
using (var dlg = new FolderBrowserDialog())
{
dlg.Description = "Seleziona la cartella di esportazione CSV";
if (dlg.ShowDialog() == DialogResult.OK)
txtExportPath.Text = dlg.SelectedPath;
}
}
// Carica il modello ML
try
private void btnSaveSettings_Click(object sender, EventArgs e)
{
labelStatusHorse.Text = "Caricamento del modello ML...";
mlService.LoadModel();
labelStatusHorse.Text = "Modello ML caricato";
}
catch (Exception ex)
{
MessageBox.Show("Impossibile caricare il modello ML:\n" + ex.Message, "Errore ML", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Genera le predizioni
GeneratePredictions();
}
// Metodo per generare predizioni in base ai dati importati o dal database
private void GeneratePredictions()
{
DataTable workingTable = importedHorsesData;
if (workingTable == null || workingTable.Rows.Count == 0)
{
// Se non ci sono dati importati, carica dal database
workingTable = horseDbManager.GetAllHorseRaceData();
}
if (workingTable == null || workingTable.Rows.Count == 0)
{
MessageBox.Show("Nessun dato disponibile per la predizione.", "Nessun dato", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
try
{
// Assicurati che il modello ML sia caricato
if (mlService == null)
{
MessageBox.Show("Errore: servizio ML non inizializzato.", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Controlla se la colonna delle previsioni esiste già
if (!workingTable.Columns.Contains("Posizione Prevista"))
workingTable.Columns.Add("Posizione Prevista", typeof(float));
// Copia i dati per lavorare su una nuova tabella
DataTable predictedTable = workingTable.Copy();
// Inizializza la progress bar
progressBarHorse.Minimum = 0;
progressBarHorse.Maximum = predictedTable.Rows.Count;
progressBarHorse.Value = 0;
labelStatusHorse.Text = "Inizializzazione predizioni...";
buttonPredict.Enabled = false;
Task.Run(() =>
{
for (int i = 0; i < predictedTable.Rows.Count; i++)
{
var row = predictedTable.Rows[i];
this.Invoke(new Action(() =>
{
progressBarHorse.Value = i + 1;
labelStatusHorse.Text = $"Generazione predizioni {i + 1} di {predictedTable.Rows.Count}";
}));
try
{
var mlInput = new ModelInput
{
Age = Convert.ToInt32(row["Età"] != DBNull.Value ? row["Età"] : 0),
HandicapRating = 0,
Weight = row["Peso"] != DBNull.Value ? Convert.ToSingle(row["Peso"]) : 0,
WeightCarried = row["Peso"] != DBNull.Value ? Convert.ToSingle(row["Peso"]) : 0,
Barrier = 0,
CareerRuns = 0,
CareerWins = 0,
CareerStrikeRate = 0,
CareerROI = 0,
ThisTrackRuns = 0,
ThisTrackWins = 0,
ThisTrackStrikeRate = 0,
ThisDistanceRuns = 0,
ThisDistanceWins = 0,
ThisDistanceStrikeRate = 0,
JockeyLast100Wins = 0,
JockeyLast100StrikeRate = 0,
TrainerLast100Wins = 0,
TrainerLast100StrikeRate = 0,
BestFixedOdds = row["Quota"] != DBNull.Value ? Convert.ToSingle(row["Quota"]) : 0
};
float? predicted = null;
try
{
var prediction = mlService.PredictFinishPosition(mlInput);
predicted = prediction?.PredictedPosition;
}
catch (Exception ex)
{
Console.WriteLine($"Errore ML.NET: {ex.Message}\n{ex.StackTrace}");
}
row["Posizione Prevista"] = predicted.HasValue ? (object)predicted.Value : DBNull.Value;
}
catch (Exception ex)
{
Console.WriteLine($"Errore durante la predizione per riga {i}: {ex.Message}");
}
}
this.Invoke(new Action(() =>
{
importedHorsesData = predictedTable;
dataGridViewHorse.DataSource = importedHorsesData;
FormatHorseDataGrid(dataGridViewHorse);
ShowStrikeRateStats(predictedTable);
labelStatusHorse.Text = $"Predizioni completate: {predictedTable.Rows.Count} cavalli analizzati";
buttonPredict.Enabled = true;
}));
});
}
catch (Exception ex)
{
MessageBox.Show($"Errore durante la generazione delle predizioni: {ex.Message}", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
labelStatusHorse.Text = "Errore durante la generazione delle predizioni";
buttonPredict.Enabled = true;
}
}
private void buttonImport_Click(object sender, EventArgs e)
{
// Verifica che sia stato selezionato un percorso
if (string.IsNullOrEmpty(textBoxFolderPath.Text))
{
MessageBox.Show("Seleziona prima una cartella contenente i file CSV.",
"Percorso mancante", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// Inizia il processo di importazione
ImportCsvFilesHorse(textBoxFolderPath.Text);
SaveSettings();
}
#endregion

View File

@@ -0,0 +1,475 @@
<Window x:Class="HorseRacingPredictor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Betting Predictor" Width="1100" Height="680"
MinWidth="900" MinHeight="540"
WindowStartupLocation="CenterScreen"
Background="#1E1E2E"
Loaded="Window_Loaded">
<Window.Resources>
<!-- =========================================================
CATPPUCCIN MOCHA PALETTE
========================================================= -->
<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}"/>
<!-- =========================================================
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>
</Window.Resources>
<!-- ================================================================
MAIN LAYOUT : Sidebar | Content
================================================================ -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ============= SIDEBAR ============= -->
<Border Grid.Column="0" Background="#181825">
<DockPanel LastChildFill="False">
<!-- App icon -->
<TextBlock DockPanel.Dock="Top" Text="BP"
FontFamily="Segoe UI Black" FontSize="18"
Foreground="{StaticResource BrBlue}"
HorizontalAlignment="Center" Margin="0,16,0,12"/>
<Border DockPanel.Dock="Top" Height="1" Background="#37394E" Margin="10,0"/>
<!-- Nav icons -->
<StackPanel DockPanel.Dock="Top" Margin="0,8,0,0">
<RadioButton x:Name="navFootball" Style="{StaticResource NavBtn}"
IsChecked="True" ToolTip="Calcio"
Checked="navFootball_Checked">
<TextBlock Text="&#xE8D6;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<RadioButton x:Name="navRacing" Style="{StaticResource NavBtn}"
ToolTip="Corse Cavalli"
Checked="navRacing_Checked">
<TextBlock Text="&#xE7C1;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<RadioButton x:Name="navSettings" Style="{StaticResource NavBtn}"
ToolTip="Impostazioni"
Checked="navSettings_Checked">
<TextBlock Text="&#xE713;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<RadioButton x:Name="navInfo" Style="{StaticResource NavBtn}"
ToolTip="Informazioni"
Checked="navInfo_Checked">
<TextBlock Text="&#xE946;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
</StackPanel>
</DockPanel>
</Border>
<!-- ============= CONTENT ============= -->
<DockPanel Grid.Column="1">
<!-- Header -->
<Border DockPanel.Dock="Top" Height="52" Background="#181825">
<TextBlock x:Name="lblTitle" Text="Calcio"
FontFamily="Segoe UI Semibold" FontSize="20"
Foreground="{StaticResource BrText}"
VerticalAlignment="Center" Margin="24,0"/>
</Border>
<Border DockPanel.Dock="Top" Height="1" Background="#37394E"/>
<!-- Page host -->
<Grid Background="{StaticResource BrBase}">
<!-- ==== PAGE: FOOTBALL ==== -->
<Grid x:Name="pageFootball" Margin="24,16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Toolbar -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12">
<DatePicker x:Name="dpFootball" Width="160"
Background="{StaticResource BrSurface0}"
Foreground="{StaticResource BrText}"
FontSize="13" VerticalContentAlignment="Center"/>
<Button x:Name="btnDownloadFb" Content="Scarica Partite"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
Click="btnDownloadFb_Click"/>
<Button x:Name="btnExportFbCsv" Content="Esporta CSV"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
IsEnabled="False"
Click="btnExportFbCsv_Click"/>
</StackPanel>
<!-- Status -->
<StackPanel Grid.Row="1" Margin="0,0,0,8">
<ProgressBar x:Name="pbFootball" Style="{StaticResource ModernPb}" Margin="0,0,0,4"/>
<TextBlock x:Name="lblStatusFb" Text="Pronto"
FontSize="12" Foreground="{StaticResource BrSubtext0}"/>
</StackPanel>
<!-- Grid -->
<DataGrid x:Name="dgFootball" Grid.Row="2" Style="{StaticResource ModernDg}"/>
</Grid>
<!-- ==== PAGE: HORSE RACING ==== -->
<Grid x:Name="pageRacing" Margin="24,16" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Toolbar -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12">
<ComboBox x:Name="cmbDay" Width="140"
Background="{StaticResource BrSurface0}"
Foreground="{StaticResource BrText}"
FontSize="13" VerticalContentAlignment="Center"/>
<Button x:Name="btnDownloadRc" Content="Scarica Corse"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
Click="btnDownloadRc_Click"/>
<Button x:Name="btnExportRcCsv" Content="Esporta CSV"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
IsEnabled="False"
Click="btnExportRcCsv_Click"/>
</StackPanel>
<!-- Status -->
<StackPanel Grid.Row="1" Margin="0,0,0,8">
<ProgressBar x:Name="pbRacing" Style="{StaticResource ModernPb}" Margin="0,0,0,4"/>
<TextBlock x:Name="lblStatusRc" Text="Pronto"
FontSize="12" Foreground="{StaticResource BrSubtext0}"/>
</StackPanel>
<!-- Grid -->
<DataGrid x:Name="dgRacing" Grid.Row="2" Style="{StaticResource ModernDg}"/>
</Grid>
<!-- ==== PAGE: SETTINGS ==== -->
<ScrollViewer x:Name="pageSettings" Visibility="Collapsed"
VerticalScrollBarVisibility="Auto" Padding="24,16">
<StackPanel MaxWidth="600" HorizontalAlignment="Left">
<!-- FOOTBALL API -->
<TextBlock Text="Calcio &#x2014; API" FontSize="16" FontFamily="Segoe UI Semibold"
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
<StackPanel>
<TextBlock Text="API Key (api-football)" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,0,0,6"/>
<TextBox x:Name="txtApiKey" Style="{StaticResource FlatTb}"/>
<TextBlock Text="Cartella esportazione CSV" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,14,0,6"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtFbExportPath" Style="{StaticResource FlatTb}" IsReadOnly="True"/>
<Button Grid.Column="1" Content="Sfoglia..." Margin="8,0,0,0"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrSurface2}"
Foreground="{StaticResource BrText}"
Click="btnBrowseFbExport_Click"/>
</Grid>
</StackPanel>
</Border>
<!-- HORSE RACING API -->
<TextBlock Text="Corse Cavalli &#x2014; API" FontSize="16" FontFamily="Segoe UI Semibold"
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
<StackPanel>
<TextBlock Text="Username" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,0,0,6"/>
<TextBox x:Name="txtRacingUser" Style="{StaticResource FlatTb}"/>
<TextBlock Text="Password" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,14,0,6"/>
<PasswordBox x:Name="txtRacingPass" Style="{StaticResource FlatPb}"/>
<TextBlock Text="Cartella esportazione CSV" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,14,0,6"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtRcExportPath" Style="{StaticResource FlatTb}" IsReadOnly="True"/>
<Button Grid.Column="1" Content="Sfoglia..." Margin="8,0,0,0"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrSurface2}"
Foreground="{StaticResource BrText}"
Click="btnBrowseRcExport_Click"/>
</Grid>
</StackPanel>
</Border>
<!-- SAVE -->
<Button Content="Salva impostazioni"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrGreen}"
HorizontalAlignment="Left" Margin="0,4,0,24"
Click="btnSaveSettings_Click"/>
</StackPanel>
</ScrollViewer>
<!-- ==== PAGE: INFO ==== -->
<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="&#x00A9; 2025 - Tutti i diritti riservati"/>
</TextBlock>
</StackPanel>
</Border>
</Grid>
</Grid>
</DockPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,326 @@
using System;
using System.Data;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace HorseRacingPredictor
{
public partial class MainWindow : Window
{
private readonly Football.Main _footballManager;
private HorseRacing.Main _racingManager;
private DataTable _footballData;
private DataTable _racingData;
private const string DefaultRacingUser = "qi1mHOHPquDY9KNDASAeGipy";
private const string DefaultRacingPass = "RXNFU1YX27R9rTnk8Vop8ZfH";
public MainWindow()
{
InitializeComponent();
_footballManager = new Football.Main();
_racingManager = new HorseRacing.Main(DefaultRacingUser, DefaultRacingPass);
}
// ???????????????????? LIFECYCLE ????????????????????
private void Window_Loaded(object sender, RoutedEventArgs e)
{
dpFootball.SelectedDate = DateTime.Today;
cmbDay.Items.Add("Oggi");
cmbDay.Items.Add("Domani");
cmbDay.SelectedIndex = 0;
LoadSettings();
}
// ???????????????????? NAVIGATION ????????????????????
private void ShowPage(string name)
{
// Guard against UI elements not being initialized (possible when called early)
if (pageFootball != null) pageFootball.Visibility = name == "football" ? 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 (pageInfo != null) pageInfo.Visibility = name == "info" ? Visibility.Visible : Visibility.Collapsed;
// Update title if available
if (lblTitle != null)
{
switch (name)
{
case "football": lblTitle.Text = "Calcio"; break;
case "racing": lblTitle.Text = "Corse Cavalli"; break;
case "settings": lblTitle.Text = "Impostazioni"; break;
case "info": lblTitle.Text = "Informazioni"; break;
}
}
}
private void navFootball_Checked(object sender, RoutedEventArgs e) => ShowPage("football");
private void navRacing_Checked(object sender, RoutedEventArgs e) => ShowPage("racing");
private void navSettings_Checked(object sender, RoutedEventArgs e) => ShowPage("settings");
private void navInfo_Checked(object sender, RoutedEventArgs e) => ShowPage("info");
// ???????????????????? FOOTBALL ????????????????????
private async void btnDownloadFb_Click(object sender, RoutedEventArgs e)
{
var date = dpFootball.SelectedDate ?? DateTime.Today;
await DownloadFootballAsync(date);
}
private async Task DownloadFootballAsync(DateTime date)
{
try
{
pbFootball.Value = 0;
lblStatusFb.Text = "Scaricamento elenco partite…";
btnDownloadFb.IsEnabled = false;
dpFootball.IsEnabled = false;
btnExportFbCsv.IsEnabled = false;
var progress = new Progress<int>(v => pbFootball.Value = v);
var status = new Progress<string>(s => lblStatusFb.Text = s);
var table = await Task.Run(() =>
_footballManager.GetTodayFixtures(date, progress, status));
_footballData = table;
dgFootball.ItemsSource = _footballData?.DefaultView;
if (_footballData != null && _footballData.Rows.Count > 0)
{
btnExportFbCsv.IsEnabled = true;
lblStatusFb.Text = $"Scaricate {_footballData.Rows.Count} partite";
}
else
{
lblStatusFb.Text = "Nessuna partita trovata per la data selezionata";
}
}
catch (Exception ex)
{
MessageBox.Show($"Errore durante lo scaricamento:\n{ex.Message}",
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
lblStatusFb.Text = "Errore nello scaricamento";
pbFootball.Value = 0;
}
finally
{
btnDownloadFb.IsEnabled = true;
dpFootball.IsEnabled = true;
}
}
private void btnExportFbCsv_Click(object sender, RoutedEventArgs e)
{
ExportToCsv(_footballData, txtFbExportPath.Text,
$"Partite_{dpFootball.SelectedDate:yyyy-MM-dd}.csv",
s => lblStatusFb.Text = s);
}
// ???????????????????? HORSE RACING ????????????????????
private async void btnDownloadRc_Click(object sender, RoutedEventArgs e)
{
await DownloadRacecardsAsync();
}
private async Task DownloadRacecardsAsync()
{
try
{
pbRacing.Value = 0;
lblStatusRc.Text = "Scaricamento racecard…";
btnDownloadRc.IsEnabled = false;
cmbDay.IsEnabled = false;
btnExportRcCsv.IsEnabled = false;
var progress = new Progress<int>(v => pbRacing.Value = v);
var status = new Progress<string>(s => lblStatusRc.Text = s);
string day = cmbDay.SelectedIndex == 0 ? "today" : "tomorrow";
var table = await Task.Run(() =>
_racingManager.GetRacecards(day, progress, status));
_racingData = table;
dgRacing.ItemsSource = _racingData?.DefaultView;
if (_racingData != null && _racingData.Rows.Count > 0)
{
btnExportRcCsv.IsEnabled = true;
lblStatusRc.Text = $"Trovate {_racingData.Rows.Count} corse";
}
else
{
lblStatusRc.Text = "Nessuna corsa trovata";
}
}
catch (Exception ex)
{
MessageBox.Show($"Errore durante lo scaricamento:\n{ex.Message}",
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
lblStatusRc.Text = "Errore nello scaricamento";
pbRacing.Value = 0;
}
finally
{
btnDownloadRc.IsEnabled = true;
cmbDay.IsEnabled = true;
}
}
private void btnExportRcCsv_Click(object sender, RoutedEventArgs e)
{
string dayLabel = cmbDay.SelectedIndex == 0 ? "oggi" : "domani";
ExportToCsv(_racingData, txtRcExportPath.Text,
$"Corse_{dayLabel}_{DateTime.Now:yyyy-MM-dd}.csv",
s => lblStatusRc.Text = s);
}
// ???????????????????? SHARED CSV EXPORT ????????????????????
private void ExportToCsv(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;
}
string filePath;
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder))
{
filePath = Path.Combine(folder, defaultName);
}
else
{
var dlg = new Microsoft.Win32.SaveFileDialog
{
Filter = "File CSV|*.csv",
FileName = defaultName
};
if (dlg.ShowDialog() != true) return;
filePath = dlg.FileName;
}
try
{
var sb = new StringBuilder();
var headers = new string[data.Columns.Count];
for (int i = 0; i < data.Columns.Count; i++)
headers[i] = data.Columns[i].ColumnName;
sb.AppendLine(string.Join(";", headers));
foreach (DataRow row in data.Rows)
{
var vals = new string[data.Columns.Count];
for (int i = 0; i < data.Columns.Count; i++)
{
var v = row[i]?.ToString() ?? "";
if (v.Contains(";") || v.Contains("\""))
v = "\"" + v.Replace("\"", "\"\"") + "\"";
vals[i] = v;
}
sb.AppendLine(string.Join(";", vals));
}
File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
setStatus?.Invoke($"CSV 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 CSV:\n{ex.Message}",
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// ???????????????????? FOLDER BROWSE ????????????????????
private void btnBrowseFbExport_Click(object sender, RoutedEventArgs e)
{
var path = BrowseFolder("Seleziona la cartella di esportazione per Calcio");
if (path != null) txtFbExportPath.Text = path;
}
private void btnBrowseRcExport_Click(object sender, RoutedEventArgs e)
{
var path = BrowseFolder("Seleziona la cartella di esportazione per Corse Cavalli");
if (path != null) txtRcExportPath.Text = path;
}
private static string BrowseFolder(string description)
{
using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
{
dlg.Description = description;
if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
return dlg.SelectedPath;
}
return null;
}
// ???????????????????? SETTINGS ????????????????????
private string SettingsFilePath =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.ini");
private void LoadSettings()
{
try
{
txtRacingUser.Text = DefaultRacingUser;
txtRacingPass.Password = DefaultRacingPass;
if (!File.Exists(SettingsFilePath)) return;
foreach (var line in File.ReadAllLines(SettingsFilePath))
{
var idx = line.IndexOf('=');
if (idx < 0) continue;
var key = line.Substring(0, idx).Trim();
var val = line.Substring(idx + 1).Trim();
if (key == "ApiKey") txtApiKey.Text = val;
else if (key == "FbExportPath") txtFbExportPath.Text = val;
else if (key == "RcExportPath") txtRcExportPath.Text = val;
else if (key == "RacingUser") txtRacingUser.Text = val;
else if (key == "RacingPass") txtRacingPass.Password = val;
}
_racingManager.UpdateCredentials(txtRacingUser.Text, txtRacingPass.Password);
}
catch { }
}
private void btnSaveSettings_Click(object sender, RoutedEventArgs e)
{
try
{
var sb = new StringBuilder();
sb.AppendLine($"ApiKey={txtApiKey.Text.Trim()}");
sb.AppendLine($"FbExportPath={txtFbExportPath.Text.Trim()}");
sb.AppendLine($"RcExportPath={txtRcExportPath.Text.Trim()}");
sb.AppendLine($"RacingUser={txtRacingUser.Text.Trim()}");
sb.AppendLine($"RacingPass={txtRacingPass.Password.Trim()}");
File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8);
_racingManager.UpdateCredentials(txtRacingUser.Text.Trim(), txtRacingPass.Password.Trim());
MessageBox.Show("Impostazioni salvate con successo.",
"Salvato", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Errore nel salvataggio:\n{ex.Message}",
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}

View File

@@ -0,0 +1,40 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// Pannello con angoli arrotondati e ombra simulata per stile card moderno
/// </summary>
internal class CardPanel : Panel
{
public int Radius { get; set; } = ModernTheme.CardRadius;
public CardPanel()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true);
BackColor = Color.Transparent;
Padding = new Padding(ModernTheme.CardPadding);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Pulisci sfondo
using (var bgBrush = new SolidBrush(Parent?.BackColor ?? ModernTheme.ContentBackground))
g.FillRectangle(bgBrush, ClientRectangle);
var cardRect = new Rectangle(0, 0, Width - 2, Height - 2);
using (var path = ModernTheme.GetRoundedRectPath(cardRect, Radius))
{
using (var brush = new SolidBrush(ModernTheme.CardBackground))
g.FillPath(brush, path);
using (var pen = new Pen(ModernTheme.CardBorder, 1))
g.DrawPath(pen, path);
}
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// Pulsante con stile moderno (arrotondato, con effetti hover)
/// </summary>
internal class ModernButton : Button
{
private bool _isHovered;
private bool _isPressed;
private Color _accentColor;
public Color AccentColor
{
get => _accentColor;
set { _accentColor = value; Invalidate(); }
}
public bool IsPrimary { get; set; } = true;
public ModernButton()
{
_accentColor = ModernTheme.PrimaryColor;
FlatStyle = FlatStyle.Flat;
FlatAppearance.BorderSize = 0;
Font = new Font("Segoe UI Semibold", 10F);
ForeColor = Color.White;
Cursor = Cursors.Hand;
Height = 36;
MinimumSize = new Size(100, 36);
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.Opaque, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
// Pulisci sfondo
using (var bgBrush = new SolidBrush(Parent?.BackColor ?? ModernTheme.ContentBackground))
g.FillRectangle(bgBrush, ClientRectangle);
var rect = new Rectangle(0, 0, Width - 1, Height - 1);
Color bgColor;
if (!Enabled)
bgColor = Color.FromArgb(100, _accentColor);
else if (_isPressed)
bgColor = ControlPaint.Dark(_accentColor, 0.15f);
else if (_isHovered)
bgColor = ControlPaint.Light(_accentColor, 0.15f);
else
bgColor = _accentColor;
using (var path = ModernTheme.GetRoundedRectPath(rect, 6))
{
if (IsPrimary)
{
using (var brush = new SolidBrush(bgColor))
g.FillPath(brush, path);
}
else
{
using (var brush = new SolidBrush(ModernTheme.CardBackground))
g.FillPath(brush, path);
using (var pen = new Pen(bgColor, 2))
g.DrawPath(pen, path);
}
var textColor = IsPrimary ? Color.FromArgb(24, 24, 37) : ModernTheme.TextPrimary;
using (var textBrush = new SolidBrush(Enabled ? textColor : Color.FromArgb(80, textColor)))
{
var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
g.DrawString(Text, Font, textBrush, rect, sf);
}
}
}
protected override void OnMouseEnter(EventArgs e) { _isHovered = true; Invalidate(); base.OnMouseEnter(e); }
protected override void OnMouseLeave(EventArgs e) { _isHovered = false; _isPressed = false; Invalidate(); base.OnMouseLeave(e); }
protected override void OnMouseDown(MouseEventArgs e) { _isPressed = true; Invalidate(); base.OnMouseDown(e); }
protected override void OnMouseUp(MouseEventArgs e) { _isPressed = false; Invalidate(); base.OnMouseUp(e); }
}
}

View File

@@ -0,0 +1,53 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// ProgressBar moderna con stile piatto e bordi arrotondati
/// </summary>
internal class ModernProgressBar : ProgressBar
{
public Color BarColor { get; set; } = ModernTheme.PrimaryColor;
public Color TrackColor { get; set; } = Color.FromArgb(49, 50, 68);
public ModernProgressBar()
{
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.Opaque, true);
Height = 8;
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Pulisci sfondo
using (var bgBrush = new SolidBrush(Parent?.BackColor ?? ModernTheme.ContentBackground))
g.FillRectangle(bgBrush, ClientRectangle);
var rect = new Rectangle(0, 0, Width - 1, Height - 1);
using (var path = ModernTheme.GetRoundedRectPath(rect, Height / 2))
using (var brush = new SolidBrush(TrackColor))
{
g.FillPath(brush, path);
}
if (Maximum > Minimum && Value > Minimum)
{
float fraction = (float)(Value - Minimum) / (Maximum - Minimum);
int fillWidth = (int)(rect.Width * fraction);
if (fillWidth > 4)
{
var fillRect = new Rectangle(0, 0, fillWidth, Height - 1);
using (var path = ModernTheme.GetRoundedRectPath(fillRect, Height / 2))
using (var brush = new LinearGradientBrush(fillRect, BarColor, ControlPaint.Light(BarColor, 0.3f), LinearGradientMode.Horizontal))
{
g.FillPath(brush, path);
}
}
}
}
}
}

View File

@@ -1,74 +1,115 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// Gestione del tema scuro moderno per l'applicazione
/// Tema grafico moderno scuro per l'applicazione
/// </summary>
public static class ModernTheme
internal static class ModernTheme
{
// Colori principali del tema scuro
public static class Colors
// Palette colori — DARK THEME
public static readonly Color SidebarBackground = Color.FromArgb(24, 24, 37);
public static readonly Color SidebarHover = Color.FromArgb(40, 40, 58);
public static readonly Color SidebarActive = Color.FromArgb(137, 180, 250);
public static readonly Color SidebarText = Color.FromArgb(186, 194, 222);
public static readonly Color SidebarActiveText = Color.FromArgb(24, 24, 37);
public static readonly Color ContentBackground = Color.FromArgb(30, 30, 46);
public static readonly Color CardBackground = Color.FromArgb(40, 42, 58);
public static readonly Color CardBorder = Color.FromArgb(55, 57, 78);
public static readonly Color HeaderBackground = Color.FromArgb(24, 24, 37);
public static readonly Color PrimaryColor = Color.FromArgb(137, 180, 250);
public static readonly Color PrimaryDark = Color.FromArgb(116, 155, 220);
public static readonly Color SuccessColor = Color.FromArgb(166, 218, 149);
public static readonly Color WarningColor = Color.FromArgb(249, 226, 175);
public static readonly Color DangerColor = Color.FromArgb(243, 139, 168);
public static readonly Color TextPrimary = Color.FromArgb(205, 214, 244);
public static readonly Color TextSecondary = Color.FromArgb(147, 153, 178);
public static readonly Color InputBackground = Color.FromArgb(49, 50, 68);
public static readonly Color InputBorder = Color.FromArgb(69, 71, 90);
public static readonly Color InputText = Color.FromArgb(205, 214, 244);
// Font
public static readonly Font TitleFont = new Font("Segoe UI Semibold", 16F);
public static readonly Font SubtitleFont = new Font("Segoe UI Semibold", 12F);
public static readonly Font BodyFont = new Font("Segoe UI", 10F);
public static readonly Font SmallFont = new Font("Segoe UI", 9F);
public static readonly Font NavFont = new Font("Segoe UI Semibold", 11F);
// Dimensioni
public const int SidebarWidth = 200;
public const int CardRadius = 8;
public const int CardPadding = 16;
/// <summary>
/// Applica lo stile moderno scuro a un DataGridView
/// </summary>
public static void StyleDataGridView(DataGridView grid)
{
// Background colors
public static readonly Color PrimaryBackground = ColorTranslator.FromHtml("#1E1E1E");
public static readonly Color SecondaryBackground = ColorTranslator.FromHtml("#252526");
public static readonly Color TertiaryBackground = ColorTranslator.FromHtml("#2D2D30");
public static readonly Color HoverBackground = ColorTranslator.FromHtml("#3E3E42");
grid.BorderStyle = BorderStyle.None;
grid.BackgroundColor = CardBackground;
grid.GridColor = CardBorder;
grid.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
// Accent colors
public static readonly Color AccentPrimary = ColorTranslator.FromHtml("#007ACC");
public static readonly Color AccentSecondary = ColorTranslator.FromHtml("#00A8E8");
public static readonly Color AccentSuccess = ColorTranslator.FromHtml("#4EC9B0");
public static readonly Color AccentWarning = ColorTranslator.FromHtml("#CE9178");
public static readonly Color AccentError = ColorTranslator.FromHtml("#F48771");
grid.EnableHeadersVisualStyles = false;
grid.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None;
grid.ColumnHeadersDefaultCellStyle = new DataGridViewCellStyle
{
BackColor = Color.FromArgb(35, 36, 52),
ForeColor = PrimaryColor,
Font = new Font("Segoe UI Semibold", 10F),
Padding = new Padding(8, 4, 8, 4),
Alignment = DataGridViewContentAlignment.MiddleLeft
};
grid.ColumnHeadersHeight = 40;
// Text colors
public static readonly Color TextPrimary = ColorTranslator.FromHtml("#FFFFFF");
public static readonly Color TextSecondary = ColorTranslator.FromHtml("#CCCCCC");
public static readonly Color TextDisabled = ColorTranslator.FromHtml("#808080");
grid.DefaultCellStyle = new DataGridViewCellStyle
{
BackColor = CardBackground,
ForeColor = TextPrimary,
Font = BodyFont,
SelectionBackColor = Color.FromArgb(60, 63, 85),
SelectionForeColor = PrimaryColor,
Padding = new Padding(6, 3, 6, 3)
};
// Border colors
public static readonly Color BorderPrimary = ColorTranslator.FromHtml("#3F3F46");
public static readonly Color BorderAccent = ColorTranslator.FromHtml("#007ACC");
grid.AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle
{
BackColor = Color.FromArgb(45, 47, 64),
ForeColor = TextPrimary,
Font = BodyFont,
SelectionBackColor = Color.FromArgb(60, 63, 85),
SelectionForeColor = PrimaryColor,
Padding = new Padding(6, 3, 6, 3)
};
// Grid colors
public static readonly Color GridHeader = ColorTranslator.FromHtml("#2D2D30");
public static readonly Color GridRowEven = ColorTranslator.FromHtml("#252526");
public static readonly Color GridRowOdd = ColorTranslator.FromHtml("#1E1E1E");
public static readonly Color GridSelected = ColorTranslator.FromHtml("#094771");
public static readonly Color GridHover = ColorTranslator.FromHtml("#2A2D2E");
// Status colors
public static readonly Color StatusWin = ColorTranslator.FromHtml("#4EC9B0");
public static readonly Color StatusPlace = ColorTranslator.FromHtml("#CE9178");
public static readonly Color StatusNormal = ColorTranslator.FromHtml("#3E3E42");
grid.RowHeadersVisible = false;
grid.RowTemplate.Height = 36;
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
grid.ReadOnly = true;
grid.AllowUserToAddRows = false;
grid.AllowUserToDeleteRows = false;
grid.AllowUserToResizeRows = false;
}
// Font configurations
public static class Fonts
/// <summary>
/// Disegna un rettangolo arrotondato
/// </summary>
public static GraphicsPath GetRoundedRectPath(Rectangle rect, int radius)
{
public static readonly Font TitleFont = new Font("Segoe UI", 16F, FontStyle.Bold);
public static readonly Font SubtitleFont = new Font("Segoe UI", 12F, FontStyle.Bold);
public static readonly Font RegularFont = new Font("Segoe UI", 9.75F, FontStyle.Regular);
public static readonly Font SmallFont = new Font("Segoe UI", 8.25F, FontStyle.Regular);
public static readonly Font ButtonFont = new Font("Segoe UI", 9.75F, FontStyle.Regular);
}
// Spacing and sizing
public static class Spacing
{
public const int Small = 5;
public const int Medium = 10;
public const int Large = 15;
public const int ExtraLarge = 20;
}
public static class BorderRadius
{
public const int Small = 3;
public const int Medium = 5;
public const int Large = 8;
var path = new GraphicsPath();
int d = radius * 2;
path.AddArc(rect.X, rect.Y, d, d, 180, 90);
path.AddArc(rect.Right - d, rect.Y, d, d, 270, 90);
path.AddArc(rect.Right - d, rect.Bottom - d, d, d, 0, 90);
path.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
path.CloseFigure();
return path;
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// Pulsante di navigazione per la sidebar
/// </summary>
internal class NavButton : Button
{
private bool _isActive;
private bool _isHovered;
private readonly string _icon;
public bool IsActive
{
get => _isActive;
set { _isActive = value; Invalidate(); }
}
public NavButton(string text, string icon)
{
_icon = icon;
Text = text;
FlatStyle = FlatStyle.Flat;
FlatAppearance.BorderSize = 0;
FlatAppearance.MouseOverBackColor = Color.Transparent;
FlatAppearance.MouseDownBackColor = Color.Transparent;
Font = ModernTheme.NavFont;
ForeColor = ModernTheme.SidebarText;
BackColor = ModernTheme.SidebarBackground;
Cursor = Cursors.Hand;
Height = 44;
Dock = DockStyle.Top;
Padding = new Padding(0);
Margin = new Padding(0);
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.DoubleBuffer |
ControlStyles.Opaque, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
var rect = ClientRectangle;
// Sfondo pieno sidebar
using (var bg = new SolidBrush(ModernTheme.SidebarBackground))
g.FillRectangle(bg, rect);
// Stato attivo: rettangolo arrotondato + indicatore
if (_isActive)
{
var activeRect = new Rectangle(8, 4, rect.Width - 16, rect.Height - 8);
using (var path = ModernTheme.GetRoundedRectPath(activeRect, 6))
using (var brush = new SolidBrush(ModernTheme.SidebarActive))
g.FillPath(brush, path);
using (var bar = new SolidBrush(ModernTheme.SidebarActive))
g.FillRectangle(bar, 0, 10, 3, rect.Height - 20);
}
// Stato hover (solo se non attivo)
else if (_isHovered)
{
var hoverRect = new Rectangle(8, 4, rect.Width - 16, rect.Height - 8);
using (var path = ModernTheme.GetRoundedRectPath(hoverRect, 6))
using (var brush = new SolidBrush(ModernTheme.SidebarHover))
g.FillPath(brush, path);
}
// Colori testo/icona
var fg = _isActive ? ModernTheme.SidebarActiveText : ModernTheme.SidebarText;
// Icona
using (var iconFont = new Font("Segoe UI", 11F, FontStyle.Bold))
using (var brush = new SolidBrush(fg))
{
var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
g.DrawString(_icon, iconFont, brush, new RectangleF(14, 0, 28, rect.Height), sf);
}
// Testo
using (var brush = new SolidBrush(fg))
{
var sf = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center,
FormatFlags = StringFormatFlags.NoWrap
};
g.DrawString(Text, Font, brush, new RectangleF(46, 0, rect.Width - 54, rect.Height), sf);
}
}
protected override void OnMouseEnter(EventArgs e)
{
_isHovered = true;
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
_isHovered = false;
Invalidate();
base.OnMouseLeave(e);
}
}
}