feat: DataGrid 페이징 로직 추가

prototype
HyungJune Kim 10 months ago
parent e4dc4a2784
commit af88b6e5a3

@ -40,4 +40,13 @@ namespace SmartAquaViewer.Classes
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> !(value is bool b && b); => !(value is bool b && b);
} }
public class OneBasedConverter : IValueConverter
{
public object Convert(object value, Type t, object p, CultureInfo c) =>
value is int i ? (i + 1).ToString() : "1";
public object ConvertBack(object value, Type t, object p, CultureInfo c) =>
int.TryParse(value?.ToString(), out var v) ? Math.Max(1, v) - 1 : 0;
}
} }

@ -1,96 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows;
namespace SmartAquaViewer.Classes
{
public static class DataGridAutoBuilder
{
public static void BuildColumnsFromType<T>(DataGrid grid, IEnumerable<T> items,
Dictionary<string, string> headerMap = null, string dateFormat = "yyyy-MM-dd HH:mm:ss")
{
grid.Columns.Clear();
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var p in props)
{
DataGridColumn col = CreateColumnForProperty(p, headerMap, dateFormat);
if (col != null) grid.Columns.Add(col);
}
grid.ItemsSource = items;
}
private static DataGridColumn CreateColumnForProperty(PropertyInfo p,
Dictionary<string, string> headerMap, string dateFormat)
{
string header = headerMap != null && headerMap.TryGetValue(p.Name, out var h) ? h : p.Name;
var type = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
// bool → CheckBox
if (type == typeof(bool))
{
return new DataGridCheckBoxColumn
{
Header = header,
Binding = new Binding(p.Name) { Mode = BindingMode.TwoWay }
};
}
// enum → ComboBox(읽기/쓰기)
if (type.IsEnum)
{
return new DataGridComboBoxColumn
{
Header = header,
ItemsSource = Enum.GetValues(type),
SelectedItemBinding = new Binding(p.Name) { Mode = BindingMode.TwoWay }
};
}
// DateTime → 포맷
if (type == typeof(DateTime))
{
return new DataGridTextColumn
{
Header = header,
Binding = new Binding(p.Name)
{
Mode = BindingMode.TwoWay,
StringFormat = dateFormat
}
};
}
// 숫자 → 우측정렬
if (type == typeof(int) || type == typeof(long) ||
type == typeof(float) || type == typeof(double) || type == typeof(decimal))
{
var col = new DataGridTextColumn
{
Header = header,
Binding = new Binding(p.Name) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }
};
col.ElementStyle = new Style(typeof(TextBlock))
{
Setters = { new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Right) }
};
return col;
}
// 기본(문자 등)
return new DataGridTextColumn
{
Header = header,
Binding = new Binding(p.Name) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }
};
}
}
}

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using SmartAquaViewer.ViewModel;
namespace SmartAquaViewer.Helper
{
public static class DataGridAutoPageSizeBehavior
{
public static readonly DependencyProperty EnableProperty =
DependencyProperty.RegisterAttached(
"Enable", typeof(bool), typeof(DataGridAutoPageSizeBehavior),
new PropertyMetadata(false, OnEnableChanged));
public static void SetEnable(DependencyObject d, bool v) => d.SetValue(EnableProperty, v);
public static bool GetEnable(DependencyObject d) => (bool)d.GetValue(EnableProperty);
// 🔹 여기! 어느 페이저를 갱신할지 바인딩으로 지정
public static readonly DependencyProperty PagerProperty =
DependencyProperty.RegisterAttached(
"Pager", typeof(IPager), typeof(DataGridAutoPageSizeBehavior),
new PropertyMetadata(null));
public static void SetPager(DependencyObject d, IPager? v) => d.SetValue(PagerProperty, v);
public static IPager? GetPager(DependencyObject d) => (IPager?)d.GetValue(PagerProperty);
private static void OnEnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DataGrid dg)
{
if ((bool)e.NewValue) dg.SizeChanged += DataGrid_SizeChanged;
else dg.SizeChanged -= DataGrid_SizeChanged;
}
}
private static void DataGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sender is not DataGrid dg) return;
// 1) 우선 명시된 Pager를 사용
var pager = GetPager(dg);
// 2) 없으면 DataContext에서 찾아봄 (메인 페이저 케이스)
pager ??= dg.DataContext as IPager;
if (pager == null || dg.RowHeight <= 0 || dg.ActualHeight <= 0) return;
double header = dg.ColumnHeaderHeight > 0 ? dg.ColumnHeaderHeight : 45;
double available = Math.Max(0, dg.ActualHeight - header);
int rows = Math.Max(1, (int)(available / dg.RowHeight));
if (pager.PageSize != rows)
pager.PageSize = rows; // 페이저 쪽에서 RebuildPage() 호출됨
}
}
}

@ -173,6 +173,7 @@
<Setter Property="FontWeight" Value="Bold"/> <Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Padding" Value="3"/> <Setter Property="Padding" Value="3"/>
<Setter Property="FontFamily" Value="{StaticResource SCDream6}"/> <Setter Property="FontFamily" Value="{StaticResource SCDream6}"/>
<Setter Property="Height" Value="45"/>
</Style> </Style>
<Style x:Key="DataGridElmenetStyle" TargetType="{x:Type TextBlock}"> <Style x:Key="DataGridElmenetStyle" TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center"/> <Setter Property="HorizontalAlignment" Value="Center"/>

@ -7,11 +7,13 @@
xmlns:control="clr-namespace:SmartAquaViewer.Controls" xmlns:control="clr-namespace:SmartAquaViewer.Controls"
xmlns:helper="clr-namespace:SmartAquaViewer.Helper" xmlns:helper="clr-namespace:SmartAquaViewer.Helper"
xmlns:classes="clr-namespace:SmartAquaViewer.Classes" xmlns:classes="clr-namespace:SmartAquaViewer.Classes"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="940" d:DesignWidth="1650"> d:DesignHeight="940" d:DesignWidth="1650">
<UserControl.Resources> <UserControl.Resources>
<classes:InverseBoolConverter x:Key="InverseBoolConverter"/> <classes:InverseBoolConverter x:Key="InverseBoolConverter"/>
<classes:OneBasedConverter x:Key="OneBasedConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Border BorderBrush="#2d374c" BorderThickness="2"> <Border BorderBrush="#2d374c" BorderThickness="2">
@ -20,6 +22,7 @@
<RowDefinition Height="160"/> <RowDefinition Height="160"/>
<RowDefinition Height="350"/> <RowDefinition Height="350"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@ -88,10 +91,13 @@
</UniformGrid> </UniformGrid>
</Border> </Border>
<DataGrid Style="{StaticResource DataGridStyle}" ItemsSource="{Binding WaterQualityList}" Background="Transparent" <DataGrid ItemsSource="{Binding PagedItems}"
Style="{StaticResource DataGridStyle}" Background="Transparent"
RowStyle="{StaticResource DataGridRowStyle}" ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}" RowStyle="{StaticResource DataGridRowStyle}" ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}"
Grid.Row="1" Grid.RowSpan="2" Margin="15 0 15 15" helper:DataGridAutoPageSizeBehavior.Enable="True"
HorizontalAlignment="Stretch" ColumnWidth="*"> Grid.Row="1" Grid.RowSpan="2" Margin="15 0"
HorizontalAlignment="Stretch"
ColumnWidth="*" RowHeight="30">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn <DataGridTextColumn
Header="시간" Width="90" Header="시간" Width="90"
@ -161,6 +167,36 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
<Grid Grid.Row="3" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Command="{Binding FirstPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="PageFirst"/>
</Button>
<Button Command="{Binding PrevPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="ChevronLeft"/>
</Button>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="8,0">
<TextBlock Margin="0 5" Foreground="White"
Text="{Binding PageIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource OneBasedConverter}}"/>
<TextBlock Text="{Binding TotalPages, StringFormat=/ {0}}"
Foreground="White" VerticalAlignment="Center" Margin="6,0,0,0"/>
</StackPanel>
<Button Command="{Binding NextPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="ChevronRight"/>
</Button>
<Button Command="{Binding LastPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="PageLast"/>
</Button>
</StackPanel>
</Grid>
<Border Grid.Row="1" Grid.Column="1" Margin="0 0 15 15" CornerRadius="10" <Border Grid.Row="1" Grid.Column="1" Margin="0 0 15 15" CornerRadius="10"
Background="#384659" BorderBrush="#404F63" BorderThickness="1"> Background="#384659" BorderBrush="#404F63" BorderThickness="1">
<Grid> <Grid>
@ -365,7 +401,7 @@
</Grid> </Grid>
</Border> </Border>
<Border Grid.Row="2" Grid.Column="1" Margin="0 0 15 15" CornerRadius="10" <Border Grid.Row="2" Grid.Column="1" Grid.RowSpan="2" Margin="0 0 15 15" CornerRadius="10"
Background="#384659" BorderBrush="#404F63" BorderThickness="1"> Background="#384659" BorderBrush="#404F63" BorderThickness="1">
<control:GraphControl x:Name="graphControl" <control:GraphControl x:Name="graphControl"
Margin="10" DataContext="{Binding GraphControlVM}" Margin="10" DataContext="{Binding GraphControlVM}"

@ -7,11 +7,13 @@
xmlns:control="clr-namespace:SmartAquaViewer.Controls" xmlns:control="clr-namespace:SmartAquaViewer.Controls"
xmlns:helper="clr-namespace:SmartAquaViewer.Helper" xmlns:helper="clr-namespace:SmartAquaViewer.Helper"
xmlns:classes="clr-namespace:SmartAquaViewer.Classes" xmlns:classes="clr-namespace:SmartAquaViewer.Classes"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="940" d:DesignWidth="1650"> d:DesignHeight="940" d:DesignWidth="1650">
<UserControl.Resources> <UserControl.Resources>
<classes:InverseBoolConverter x:Key="InverseBoolConverter"/> <classes:InverseBoolConverter x:Key="InverseBoolConverter"/>
<classes:OneBasedConverter x:Key="OneBasedConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Border BorderBrush="#2d374c" BorderThickness="2"> <Border BorderBrush="#2d374c" BorderThickness="2">
@ -20,6 +22,7 @@
<RowDefinition Height="160"/> <RowDefinition Height="160"/>
<RowDefinition Height="350"/> <RowDefinition Height="350"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@ -88,10 +91,13 @@
</UniformGrid> </UniformGrid>
</Border> </Border>
<DataGrid Style="{StaticResource DataGridStyle}" Background="Transparent" ItemsSource="{Binding WaterQualityList}" <DataGrid Style="{StaticResource DataGridStyle}" Background="Transparent"
ItemsSource="{Binding PagedItems}"
RowStyle="{StaticResource DataGridRowStyle}" ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}" RowStyle="{StaticResource DataGridRowStyle}" ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}"
ColumnWidth="*" HorizontalAlignment="Stretch" helper:DataGridAutoPageSizeBehavior.Enable="True"
Grid.Row="1" Grid.RowSpan="2" Margin="15 0 15 15"> ColumnWidth="*" RowHeight="30"
HorizontalAlignment="Stretch"
Grid.Row="1" Grid.RowSpan="2" Margin="15 0">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn <DataGridTextColumn
Header="시간" Header="시간"
@ -161,6 +167,36 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
<Grid Grid.Row="3" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Command="{Binding FirstPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="PageFirst"/>
</Button>
<Button Command="{Binding PrevPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="ChevronLeft"/>
</Button>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="8,0">
<TextBlock Margin="0 5" Foreground="White"
Text="{Binding PageIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource OneBasedConverter}}"/>
<TextBlock Text="{Binding TotalPages, StringFormat=/ {0}}"
Foreground="White" VerticalAlignment="Center" Margin="6,0,0,0"/>
</StackPanel>
<Button Command="{Binding NextPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="ChevronRight"/>
</Button>
<Button Command="{Binding LastPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="PageLast"/>
</Button>
</StackPanel>
</Grid>
<Border Grid.Row="1" Grid.Column="1" Margin="0 0 15 15" CornerRadius="10" <Border Grid.Row="1" Grid.Column="1" Margin="0 0 15 15" CornerRadius="10"
Background="#384659" BorderBrush="#404F63" BorderThickness="1"> Background="#384659" BorderBrush="#404F63" BorderThickness="1">
<Grid> <Grid>
@ -362,7 +398,7 @@
</Grid> </Grid>
</Border> </Border>
<Border Grid.Row="2" Grid.Column="1" Margin="0 0 15 15" CornerRadius="10" <Border Grid.Row="2" Grid.Column="1" Grid.RowSpan="2" Margin="0 0 15 15" CornerRadius="10"
Background="#384659" BorderBrush="#404F63" BorderThickness="1"> Background="#384659" BorderBrush="#404F63" BorderThickness="1">
<control:GraphControl x:Name="graphControl" <control:GraphControl x:Name="graphControl"
Margin="10" DataContext="{Binding GraphControlVM}" Margin="10" DataContext="{Binding GraphControlVM}"

@ -15,6 +15,7 @@
<UserControl.Resources> <UserControl.Resources>
<classes:EnumEqualsConverter x:Key="EnumEqualsConverter"/> <classes:EnumEqualsConverter x:Key="EnumEqualsConverter"/>
<classes:BoolToPowerConverter x:Key="BoolToPowerConverter"/> <classes:BoolToPowerConverter x:Key="BoolToPowerConverter"/>
<classes:OneBasedConverter x:Key="OneBasedConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Border BorderBrush="#2d374c" BorderThickness="2"> <Border BorderBrush="#2d374c" BorderThickness="2">
@ -23,6 +24,8 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="70"/> <RowDefinition Height="70"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid> <Grid>
<!--<Grid.Background> <!--<Grid.Background>
@ -32,12 +35,14 @@
SelectedTab="{Binding SelectedTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> SelectedTab="{Binding SelectedTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid> </Grid>
<DataGrid x:Name="dgTanks" ItemsSource="{Binding TanksByTimes}" <DataGrid x:Name="dgTanks" ItemsSource="{Binding TanksPager.PagedItems}"
Style="{StaticResource DataGridStyle}" Style="{StaticResource DataGridStyle}"
RowStyle="{StaticResource DataGridRowStyle}" RowStyle="{StaticResource DataGridRowStyle}"
ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}" ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}"
Grid.Row="1" Margin="15 0 15 40" helper:DataGridAutoPageSizeBehavior.Enable="True"
ColumnWidth="*" helper:DataGridAutoPageSizeBehavior.Pager="{Binding TanksPager}"
Grid.Row="1" Margin="15 0"
ColumnWidth="*" RowHeight="30"
Background="Transparent" Background="Transparent"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<DataGrid.Columns> <DataGrid.Columns>
@ -94,7 +99,7 @@
<DataGridTextColumn Header="번호" Binding="{Binding Tanks[1].Number}" <DataGridTextColumn Header="번호" Binding="{Binding Tanks[1].Number}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Binding="{Binding Tanks[1].DOValue, StringFormat=\{0:F2\}}" <DataGridTextColumn Binding="{Binding Tanks[1].DOValue, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="DO" HorizontalAlignment="Center"/> <TextBlock Text="DO" HorizontalAlignment="Center"/>
@ -103,9 +108,9 @@
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="pH" Binding="{Binding Tanks[1].PH, StringFormat=\{0:F2\}}" <DataGridTextColumn Header="pH" Binding="{Binding Tanks[1].PH, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Binding="{Binding Tanks[1].ORP, StringFormat=\{0:F0\}}" <DataGridTextColumn Binding="{Binding Tanks[1].ORP, StringFormat=\{0:F0\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="ORP" HorizontalAlignment="Center"/> <TextBlock Text="ORP" HorizontalAlignment="Center"/>
@ -114,7 +119,7 @@
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[1].Temperature, StringFormat=\{0:F1\}}" <DataGridTextColumn Binding="{Binding Tanks[1].Temperature, StringFormat=\{0:F1\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="온도" HorizontalAlignment="Center"/> <TextBlock Text="온도" HorizontalAlignment="Center"/>
@ -123,7 +128,7 @@
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[1].FlowRate, StringFormat=\{0:F2\}}" <DataGridTextColumn Binding="{Binding Tanks[1].FlowRate, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="유량" HorizontalAlignment="Center"/> <TextBlock Text="유량" HorizontalAlignment="Center"/>
@ -136,7 +141,7 @@
<DataGridTextColumn Header="번호" Binding="{Binding Tanks[2].Number}" <DataGridTextColumn Header="번호" Binding="{Binding Tanks[2].Number}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Binding="{Binding Tanks[2].DOValue, StringFormat=\{0:F2\}}" <DataGridTextColumn Binding="{Binding Tanks[2].DOValue, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="DO" HorizontalAlignment="Center"/> <TextBlock Text="DO" HorizontalAlignment="Center"/>
@ -145,9 +150,9 @@
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="pH" Binding="{Binding Tanks[2].PH, StringFormat=\{0:F2\}}" <DataGridTextColumn Header="pH" Binding="{Binding Tanks[2].PH, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Binding="{Binding Tanks[2].ORP, StringFormat=\{0:F0\}}" <DataGridTextColumn Binding="{Binding Tanks[2].ORP, StringFormat=\{0:F0\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="ORP" HorizontalAlignment="Center"/> <TextBlock Text="ORP" HorizontalAlignment="Center"/>
@ -156,7 +161,7 @@
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[2].Temperature, StringFormat=\{0:F1\}}" <DataGridTextColumn Binding="{Binding Tanks[2].Temperature, StringFormat=\{0:F1\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="온도" HorizontalAlignment="Center"/> <TextBlock Text="온도" HorizontalAlignment="Center"/>
@ -165,7 +170,7 @@
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[2].FlowRate, StringFormat=\{0:F2\}}" <DataGridTextColumn Binding="{Binding Tanks[2].FlowRate, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="유량" HorizontalAlignment="Center"/> <TextBlock Text="유량" HorizontalAlignment="Center"/>
@ -176,9 +181,11 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
<DataGrid ItemsSource="{Binding WaterQualityList}" x:Name="dgFilter" <DataGrid ItemsSource="{Binding PagedItems}" x:Name="dgFilter"
Style="{StaticResource DataGridStyle}" ColumnWidth="*" Style="{StaticResource DataGridStyle}"
Grid.Row="1" Margin="15 0 15 40" ColumnWidth="*" RowHeight="30"
helper:DataGridAutoPageSizeBehavior.Enable="True"
Grid.Row="1" Margin="15 0 15 0"
Background="Transparent" Background="Transparent"
RowStyle="{StaticResource DataGridRowStyle}" RowStyle="{StaticResource DataGridRowStyle}"
ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}"> ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}">
@ -299,9 +306,11 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
<DataGrid ItemsSource="{Binding WaterQualityList}" x:Name="dgSterilizer" <DataGrid ItemsSource="{Binding PagedItems}" x:Name="dgSterilizer"
Style="{StaticResource DataGridStyle}" ColumnWidth="*" Style="{StaticResource DataGridStyle}"
Grid.Row="1" Margin="15 0 15 40" ColumnWidth="*" RowHeight="30"
helper:DataGridAutoPageSizeBehavior.Enable="True"
Grid.Row="1" Margin="15 0 15 0"
Background="Transparent" Background="Transparent"
VerticalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
RowStyle="{StaticResource DataGridRowStyle}" RowStyle="{StaticResource DataGridRowStyle}"
@ -327,7 +336,89 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
<Grid Grid.Row="1" VerticalAlignment="Bottom"> <Grid Grid.Row="2">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedTab}" Value="{x:Static model:MonitorTab.Tank}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Command="{Binding TanksPager.FirstPageCommand}" Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="PageFirst"/>
</Button>
<Button Command="{Binding TanksPager.PrevPageCommand}" Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="ChevronLeft"/>
</Button>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="8,0">
<TextBlock Margin="0 5" Foreground="White"
Text="{Binding TanksPager.PageIndex, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource OneBasedConverter}}"/>
<TextBlock Text="{Binding TanksPager.TotalPages, StringFormat=/ {0}}"
Foreground="White" VerticalAlignment="Center" Margin="6,0,0,0"/>
</StackPanel>
<Button Command="{Binding TanksPager.NextPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="ChevronRight"/>
</Button>
<Button Command="{Binding TanksPager.LastPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="PageLast"/>
</Button>
</StackPanel>
</Grid>
<!-- (2) 필터/살균기 공용 페이지바 -->
<Grid Grid.Row="2">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedTab}" Value="{x:Static model:MonitorTab.Tank}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Command="{Binding FirstPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="PageFirst"/>
</Button>
<Button Command="{Binding PrevPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="ChevronLeft"/>
</Button>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="8,0">
<TextBlock Margin="0 5" Foreground="White"
Text="{Binding PageIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource OneBasedConverter}}"/>
<TextBlock Text="{Binding TotalPages, StringFormat=/ {0}}"
Foreground="White" VerticalAlignment="Center" Margin="6,0,0,0"/>
</StackPanel>
<Button Command="{Binding NextPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="ChevronRight"/>
</Button>
<Button Command="{Binding LastPageCommand}"
Style="{StaticResource MaterialDesignFlatLightBgButton}" Margin="4,0">
<md:PackIcon Kind="PageLast"/>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="3" VerticalAlignment="Bottom">
<Button Name="btnVisibilityDown" Tag="down" <Button Name="btnVisibilityDown" Tag="down"
Style="{StaticResource ImageButtonStyle}" Height="33" Command="{Binding ChangeDrawerStatusCommand}" Style="{StaticResource ImageButtonStyle}" Height="33" Command="{Binding ChangeDrawerStatusCommand}"
VerticalAlignment="Bottom" HorizontalAlignment="Center" Visibility="{Binding BtnVisibilityDown}" VerticalAlignment="Bottom" HorizontalAlignment="Center" Visibility="{Binding BtnVisibilityDown}"

@ -9,7 +9,7 @@ using SmartAquaViewer.Model;
namespace SmartAquaViewer.ViewModel namespace SmartAquaViewer.ViewModel
{ {
public class EnergyViewModel : INotifyPropertyChanged public class EnergyViewModel : PagingViewModelBase<WaterQualityVO>, INotifyPropertyChanged
{ {
public GraphControlViewModel GraphControlVM { get; } = new GraphControlViewModel(); public GraphControlViewModel GraphControlVM { get; } = new GraphControlViewModel();
public ObservableCollection<GraphType> GraphTypes { get; } public ObservableCollection<GraphType> GraphTypes { get; }
@ -250,15 +250,10 @@ namespace SmartAquaViewer.ViewModel
WaterQualityList = Datas.Instance.WaterQualityView; WaterQualityList = Datas.Instance.WaterQualityView;
TotalSandFilterEnergy = WaterQualityList.Sum(vo => vo.Filtering.SandFilterEnergy); SyncItemsWithSource();
TotalCirculationPumpEnergy = WaterQualityList.Sum(vo => vo.Filtering.CirculationPumpEnergy); ((INotifyCollectionChanged)WaterQualityList).CollectionChanged += OnSourceChanged;
TotalHeatPumpEnergy = WaterQualityList.Sum(vo => vo.Filtering.HeatPumpEnergy);
TotalAirBlowerEnergy = WaterQualityList.Sum(vo => vo.Filtering.AirBlowerEnergy); RecalcTotals();
TotalOzoneGeneratorEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneGeneratorEnergy);
TotalUVSterilizerEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.UVSterilizerEnergy);
TotalOzoneDissolverEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneDissolverEnergy);
TotalExcessOzoneDestroyerEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.ExcessOzoneDestroyerEnergy);
TotalEnergy = WaterQualityList.Sum(vo => vo.TotalEnergy);
SelectedKind = StepFieldKind.Energy; // 기본적으로 에너지 관련 필드만 표시 SelectedKind = StepFieldKind.Energy; // 기본적으로 에너지 관련 필드만 표시
@ -266,6 +261,8 @@ namespace SmartAquaViewer.ViewModel
RebuildAvailableFields(); RebuildAvailableFields();
RebuildFieldCandidates(); RebuildFieldCandidates();
SelectedGraphIndex = 0;
} }
private void DrawGraph(object obj) private void DrawGraph(object obj)
@ -355,8 +352,33 @@ namespace SmartAquaViewer.ViewModel
OnPropertyChanged(nameof(SelectedYFields)); OnPropertyChanged(nameof(SelectedYFields));
} }
public event PropertyChangedEventHandler? PropertyChanged; private void OnSourceChanged(object? sender, NotifyCollectionChangedEventArgs e)
private void OnPropertyChanged([CallerMemberName] string? name = null) {
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); SyncItemsWithSource();
RecalcTotals();
}
private void SyncItemsWithSource()
{
Items.Clear();
foreach (var w in WaterQualityList)
Items.Add(w);
}
private void RecalcTotals()
{
TotalSandFilterEnergy = WaterQualityList.Sum(vo => vo.Filtering.SandFilterEnergy);
TotalCirculationPumpEnergy = WaterQualityList.Sum(vo => vo.Filtering.CirculationPumpEnergy);
TotalHeatPumpEnergy = WaterQualityList.Sum(vo => vo.Filtering.HeatPumpEnergy);
TotalAirBlowerEnergy = WaterQualityList.Sum(vo => vo.Filtering.AirBlowerEnergy);
TotalOzoneGeneratorEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneGeneratorEnergy);
TotalUVSterilizerEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.UVSterilizerEnergy);
TotalOzoneDissolverEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneDissolverEnergy);
TotalExcessOzoneDestroyerEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.ExcessOzoneDestroyerEnergy);
TotalEnergy = WaterQualityList.Sum(vo => vo.TotalEnergy);
}
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> base.OnPropertyChanged(name);
} }
} }

@ -12,10 +12,11 @@ using System.Windows.Input;
using SmartAquaViewer.Controls; using SmartAquaViewer.Controls;
using SmartAquaViewer.DataAnalysis; using SmartAquaViewer.DataAnalysis;
using SmartAquaViewer.Model; using SmartAquaViewer.Model;
using static MaterialDesignThemes.Wpf.Theme.ToolBar;
namespace SmartAquaViewer.ViewModel namespace SmartAquaViewer.ViewModel
{ {
public class GreenHouseGasViewModel : INotifyPropertyChanged public class GreenHouseGasViewModel : PagingViewModelBase<WaterQualityVO>
{ {
public GraphControlViewModel GraphControlVM { get; } = new GraphControlViewModel(); public GraphControlViewModel GraphControlVM { get; } = new GraphControlViewModel();
public ObservableCollection<GraphType> GraphTypes { get; } public ObservableCollection<GraphType> GraphTypes { get; }
@ -255,16 +256,10 @@ namespace SmartAquaViewer.ViewModel
WaterQualityList = Datas.Instance.WaterQualityView; WaterQualityList = Datas.Instance.WaterQualityView;
SyncItemsWithSource();
((INotifyCollectionChanged)WaterQualityList).CollectionChanged += OnSourceChanged;
TotalSandFilterGreenhouseGas = WaterQualityList.Sum(vo => vo.Filtering.SandFilterGreenhouseGas); RecalcTotals();
TotalCirculationPumpGreenhouseGas = WaterQualityList.Sum(vo => vo.Filtering.CirculationPumpGreenhouseGas);
TotalHeatPumpGreenhouseGas = WaterQualityList.Sum(vo => vo.Filtering.HeatPumpGreenhouseGas);
TotalAirBlowerGreenhouseGas = WaterQualityList.Sum(vo => vo.Filtering.AirBlowerGreenhouseGas);
TotalOzoneGeneratorGreenhouseGas = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneGeneratorGreenhouseGas);
TotalUVSterilizerGreenhouseGas = WaterQualityList.Sum(vo => vo.Sterilizing.UVSterilizerGreenhouseGas);
TotalOzoneDissolverGreenhouseGas = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneDissolverGreenhouseGas);
TotalExcessOzoneDestroyerGreenhouseGas = WaterQualityList.Sum(vo => vo.Sterilizing.ExcessOzoneDestroyerGreenhouseGas);
TotalGreenhouseGas = WaterQualityList.Sum(vo => vo.TotalGreenhouseGas);
SelectedKind = StepFieldKind.GHG; SelectedKind = StepFieldKind.GHG;
@ -272,6 +267,8 @@ namespace SmartAquaViewer.ViewModel
RebuildAvailableFields(); RebuildAvailableFields();
RebuildFieldCandidates(); RebuildFieldCandidates();
SelectedGraphIndex = 0;
} }
private void DrawGraph(object obj) private void DrawGraph(object obj)
@ -361,8 +358,33 @@ namespace SmartAquaViewer.ViewModel
OnPropertyChanged(nameof(SelectedYFields)); OnPropertyChanged(nameof(SelectedYFields));
} }
public event PropertyChangedEventHandler? PropertyChanged; private void OnSourceChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
SyncItemsWithSource();
RecalcTotals();
}
private void SyncItemsWithSource()
{
Items.Clear();
foreach (var w in WaterQualityList)
Items.Add(w);
}
private void RecalcTotals()
{
TotalSandFilterGreenhouseGas = WaterQualityList.Sum(vo => vo.Filtering.SandFilterGreenhouseGas);
TotalCirculationPumpGreenhouseGas = WaterQualityList.Sum(vo => vo.Filtering.CirculationPumpGreenhouseGas);
TotalHeatPumpGreenhouseGas = WaterQualityList.Sum(vo => vo.Filtering.HeatPumpGreenhouseGas);
TotalAirBlowerGreenhouseGas = WaterQualityList.Sum(vo => vo.Filtering.AirBlowerGreenhouseGas);
TotalOzoneGeneratorGreenhouseGas = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneGeneratorGreenhouseGas);
TotalUVSterilizerGreenhouseGas = WaterQualityList.Sum(vo => vo.Sterilizing.UVSterilizerGreenhouseGas);
TotalOzoneDissolverGreenhouseGas = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneDissolverGreenhouseGas);
TotalExcessOzoneDestroyerGreenhouseGas = WaterQualityList.Sum(vo => vo.Sterilizing.ExcessOzoneDestroyerGreenhouseGas);
TotalGreenhouseGas = WaterQualityList.Sum(vo => vo.TotalGreenhouseGas);
}
private void OnPropertyChanged([CallerMemberName] string? name = null) private void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); => base.OnPropertyChanged(name);
} }
} }

@ -11,6 +11,9 @@ using SmartAquaViewer.Model;
namespace SmartAquaViewer.ViewModel namespace SmartAquaViewer.ViewModel
{ {
/// <summary>
/// 데이터에서 추출한 필드 정보
/// </summary>
public sealed class FieldItem public sealed class FieldItem
{ {
public string? Name { get; init; } // 바인딩 경로 키 (예: "Tank.DOValue") public string? Name { get; init; } // 바인딩 경로 키 (예: "Tank.DOValue")
@ -19,6 +22,9 @@ namespace SmartAquaViewer.ViewModel
public StepFieldKind Kind { get; init; } public StepFieldKind Kind { get; init; }
} }
/// <summary>
/// 특정 시점에 기록된 수조 데이터 묶음
/// </summary>
public class TanksByTime public class TanksByTime
{ {
public DateTime RecordedTime { get; } public DateTime RecordedTime { get; }
@ -31,7 +37,7 @@ namespace SmartAquaViewer.ViewModel
} }
} }
public class MonitoringViewModel : INotifyPropertyChanged public class MonitoringViewModel : PagingViewModelBase<WaterQualityVO>
{ {
#region Properties #region Properties
public GraphControlViewModel GraphControlVM { get; } = new GraphControlViewModel(); public GraphControlViewModel GraphControlVM { get; } = new GraphControlViewModel();
@ -44,6 +50,8 @@ namespace SmartAquaViewer.ViewModel
public Dictionary<int, ObservableCollection<WaterQualityVO>> SelectedWaterTanks { get; } = new(); public Dictionary<int, ObservableCollection<WaterQualityVO>> SelectedWaterTanks { get; } = new();
public ObservableCollection<TanksByTime> TanksByTimes { get; } = new(); public ObservableCollection<TanksByTime> TanksByTimes { get; } = new();
public PagingViewModelBase<TanksByTime> TanksPager { get; } = new();
private MonitorTab _selectedTab; private MonitorTab _selectedTab;
public MonitorTab SelectedTab public MonitorTab SelectedTab
@ -270,10 +278,8 @@ namespace SmartAquaViewer.ViewModel
RebuildFieldCandidates(); RebuildFieldCandidates();
} }
private void OnWaterQualityChanged(object? sender, NotifyCollectionChangedEventArgs e) private void OnWaterQualityChanged(object? sender, NotifyCollectionChangedEventArgs e) => RebuildAllGroups();
{
RebuildAllGroups(); // 변경될 때마다 그룹 재구성
}
private void RebuildAllGroups() private void RebuildAllGroups()
{ {
@ -289,21 +295,18 @@ namespace SmartAquaViewer.ViewModel
TankGroups = grouped; TankGroups = grouped;
OnPropertyChanged(nameof(TankGroups)); OnPropertyChanged(nameof(TankGroups));
// TankGroups를 깔끔하게 다시 구성 TanksPager.Items.Clear();
TanksByTimes.Clear();
foreach (var w in WaterQualityList) foreach (var w in WaterQualityList)
{ TanksPager.Items.Add(new TanksByTime(w.RecordedTime, w.Tanks));
var tbt = new TanksByTime(w.RecordedTime, w.Tanks);
TanksByTimes.Add(tbt);
OrderTanksBtTimeByDate();
}
}
private void OrderTanksBtTimeByDate() var ordered = TanksPager.Items.OrderBy(t => t.RecordedTime).ToList();
{ TanksPager.Items.Clear();
var ordered = TanksByTimes.OrderBy(t => t.RecordedTime).ToList(); foreach (var t in ordered) TanksPager.Items.Add(t);
TanksByTimes.Clear();
foreach (var t in ordered) TanksByTimes.Add(t); // TankGroups를 깔끔하게 다시 구성
this.Items.Clear();
foreach (var w in WaterQualityList)
this.Items.Add(w);
} }
private void DrawGraph(object obj) private void DrawGraph(object obj)
@ -536,8 +539,7 @@ namespace SmartAquaViewer.ViewModel
OnPropertyChanged(nameof(SelectedYFields)); OnPropertyChanged(nameof(SelectedYFields));
} }
public event PropertyChangedEventHandler? PropertyChanged; protected new void OnPropertyChanged([CallerMemberName] string? name = null)
private void OnPropertyChanged([CallerMemberName] string? name = null) => base.OnPropertyChanged(name);
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
} }
} }

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SmartAquaViewer.Controls;
using System.Windows.Input;
using System.Runtime.CompilerServices;
namespace SmartAquaViewer.ViewModel
{
public interface IPager
{
int PageSize { get; set; }
int PageIndex { get; set; }
int TotalPages { get; }
string PageStatus { get; }
ICommand FirstPageCommand { get; }
ICommand PrevPageCommand { get; }
ICommand NextPageCommand { get; }
ICommand LastPageCommand { get; }
}
public class PagingViewModelBase<T> : INotifyPropertyChanged, IPager
{
public ObservableCollection<T> Items { get; } = new();
private int _pageSize = 10;
public int PageSize
{
get => _pageSize;
set { if (_pageSize != value) { _pageSize = value; PageIndex = 0; OnPropertyChanged(); RebuildPage(); } }
}
private int _pageIndex; // 0-based
public int PageIndex
{
get => _pageIndex;
set { if (_pageIndex != value) { _pageIndex = Math.Max(0, Math.Min(value, TotalPages - 1)); OnPropertyChanged(nameof(PageIndex)); RebuildPage(); } }
}
public int TotalCount => Items.Count;
public int TotalPages => Math.Max(1, (int)Math.Ceiling(TotalCount / (double)PageSize));
private readonly ObservableCollection<T> _pagedItems = new();
public ReadOnlyObservableCollection<T> PagedItems { get; }
// 보기용 표시: "101150 / 4,327 (3/87)"
public string PageStatus
{
get
{
if (TotalCount == 0) return "0 / 0 (0/0)";
var start = PageIndex * PageSize + 1;
var end = Math.Min((PageIndex + 1) * PageSize, TotalCount);
return $"{start}{end} / {TotalCount:N0} ({PageIndex + 1}/{TotalPages})";
}
}
public ICommand FirstPageCommand => new RelayCommand(_ => PageIndex = 0, _ => PageIndex > 0);
public ICommand PrevPageCommand => new RelayCommand(_ => PageIndex--, _ => PageIndex > 0);
public ICommand NextPageCommand => new RelayCommand(_ => PageIndex++, _ => PageIndex < TotalPages - 1);
public ICommand LastPageCommand => new RelayCommand(_ => PageIndex = TotalPages - 1, _ => PageIndex < TotalPages - 1);
public PagingViewModelBase()
{
PagedItems = new ReadOnlyObservableCollection<T>(_pagedItems);
Items.CollectionChanged += (_, __) => RebuildPage(); // 원본이 바뀌면 재구성
RebuildPage();
}
private void RebuildPage()
{
// 현재 페이지 구간
var slice = Items.Skip(PageIndex * PageSize).Take(PageSize).ToList();
// 🔹 ObservableCollection 동기화 (Clear/Add)
_pagedItems.Clear();
foreach (var item in slice) _pagedItems.Add(item);
// 숫자/상태 텍스트 갱신
OnPropertyChanged(nameof(TotalCount));
OnPropertyChanged(nameof(TotalPages));
OnPropertyChanged(nameof(PageStatus));
// 버튼 CanExecute 즉시 반영
CommandManager.InvalidateRequerySuggested();
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Loading…
Cancel
Save