design: DataGrid 헤더 값 줄바꿈

prototype
HyungJune Kim 10 months ago
parent 89f9383e7a
commit 0396389ff9

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
@ -78,9 +79,6 @@ namespace SmartAquaViewer.Classes
private readonly Queue<AVFrame> _frameQueue = new(); // lock 불필요 private readonly Queue<AVFrame> _frameQueue = new(); // lock 불필요
private volatile bool _disposed; private volatile bool _disposed;
// 큐 최대 길이(지연 방지용): 최신 프레임 위주로 보여주기
private const int MaxQueue = 2;
private object _lockObject = new(); private object _lockObject = new();
public ImageRtspAdapter(System.Windows.Controls.Image img, string url) public ImageRtspAdapter(System.Windows.Controls.Image img, string url)
@ -120,7 +118,7 @@ namespace SmartAquaViewer.Classes
try try
{ {
AVFrame convertedFrame = vfc.Convert(frame); AVFrame convertedFrame = vfc.Convert(frame);
EnqueueFrame(convertedFrame); // 큐 삽입 (길이 제한 적용) _frameQueue.Enqueue(convertedFrame); // 큐 삽입 (길이 제한 적용)
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -135,11 +133,6 @@ namespace SmartAquaViewer.Classes
} }
} }
private void EnqueueFrame(AVFrame avf)
{
_frameQueue.Enqueue(avf);
}
private unsafe void RenderLoop() private unsafe void RenderLoop()
{ {
try try

@ -6,7 +6,7 @@
xmlns:local="clr-namespace:SmartAquaViewer.Controls" xmlns:local="clr-namespace:SmartAquaViewer.Controls"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<Border Margin="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="1"> <Border>
<Grid Background="Transparent"> <Grid Background="Transparent">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="24"></RowDefinition> <RowDefinition Height="24"></RowDefinition>
@ -24,25 +24,22 @@
<RowDefinition></RowDefinition> <RowDefinition></RowDefinition>
<RowDefinition></RowDefinition> <RowDefinition></RowDefinition>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Name="lblVideoStatus" Text="연결 없음"
FontSize="17" Foreground="#414961"></TextBlock>
</Grid> </Grid>
</Border> </Border>
<Image x:Name="imgPlayer" Grid.RowSpan="2" Stretch="Fill" <Image x:Name="imgPlayer" Grid.RowSpan="2" Stretch="Fill"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
Source="{Binding CurrentFrame}"/> <TextBlock HorizontalAlignment="Left" Grid.RowSpan="2" Margin="20, 5, 5, 0" x:Name="lblCCTVID"
<Label HorizontalAlignment="Left" Grid.RowSpan="2" Margin="20, 5, 5, 0" x:Name="lblCCTVID"
FontFamily="Verna" FontSize="15" FontWeight='Bold' Foreground="White" FontFamily="Verna" FontSize="15" FontWeight='Bold' Foreground="White"
Content="{Binding CCTVInfo.DeviceId}"/> Text="{Binding CCTVInfo.DeviceId}"/>
<Grid x:Name="grdTopMenuBar" Grid.Row="0" Background="Black" Opacity="0.5" Visibility="Hidden" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" WindowChrome.IsHitTestVisibleInChrome="True" >
</Grid>
<!--<StackPanel Name="spTopMenuBar" Visibility="Hidden" Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right"> <!--<StackPanel Name="spTopMenuBar" Visibility="Hidden" Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right">
<Image Name="imgMaximize" Source="../Images/maximize.png" Width="24" Margin="1, 5, 0, 5" MouseLeftButtonUp="imgMaximize_MouseUp" Opacity="0.7" MouseEnter="imgTopmenu_MouseEnter" MouseLeave="imgTopmenu_MouseLeave"/> <Image Name="imgMaximize" Source="../Images/maximize.png" Width="24" Margin="1, 5, 0, 5" MouseLeftButtonUp="imgMaximize_MouseUp" Opacity="0.7" MouseEnter="imgTopmenu_MouseEnter" MouseLeave="imgTopmenu_MouseLeave"/>
</StackPanel>--> </StackPanel>-->
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<StackPanel VerticalAlignment="Bottom" Orientation="Vertical"> <StackPanel VerticalAlignment="Bottom" Orientation="Vertical">
<Label HorizontalAlignment="Right" Margin="20, 5, 5, 0" x:Name="lblCCTVName" <TextBlock Text="{Binding CCTVInfo.DeviceName}" HorizontalAlignment="Right" Margin="20, 5, 5, 0" x:Name="lblCCTVName"
FontFamily="Verna" FontSize="15" FontWeight='Bold' Foreground="White" FontFamily="{StaticResource SCDream6}" FontSize="15" Foreground="White"/>
Content="{Binding CCTVInfo.DeviceName}"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Grid> </Grid>

@ -37,7 +37,7 @@
<Label Name="lblVideoStatus" Content="연결 없음" Grid.Row="0" FontSize="17" Foreground="#414961" HorizontalContentAlignment="Center"></Label> <Label Name="lblVideoStatus" Content="연결 없음" Grid.Row="0" FontSize="17" Foreground="#414961" HorizontalContentAlignment="Center"></Label>
</Grid> </Grid>
</Border> </Border>
<Image x:Name="imgPlayer" Grid.Row="0" Visibility="Collapsed" Grid.RowSpan="2" Stretch="Fill" Margin="0" <Image x:Name="imgPlayer" Visibility="Collapsed" Grid.RowSpan="2" Stretch="Fill" Margin="0"
classes:RtspImageBehavior.SourceUrl="{Binding RtspUrl}"/> classes:RtspImageBehavior.SourceUrl="{Binding RtspUrl}"/>
<TextBlock Text="{Binding DeviceId}" Margin="20 5 5 0" x:Name="lblCCTVId" <TextBlock Text="{Binding DeviceId}" Margin="20 5 5 0" x:Name="lblCCTVId"
FontFamily="{StaticResource SCDream6}" FontSize="15" Foreground="White"/> FontFamily="{StaticResource SCDream6}" FontSize="15" Foreground="White"/>

@ -97,29 +97,64 @@
Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}" Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Header="모래여과기" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Filtering.SandFilterEnergy}"/> Binding="{Binding Filtering.SandFilterEnergy, StringFormat=\{0:F2\}}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="모래" HorizontalAlignment="Center"/>
<TextBlock Text="여과기" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Header="순환펌프" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn Header="순환펌프" ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Filtering.CirculationPumpEnergy}"/> Binding="{Binding Filtering.CirculationPumpEnergy, StringFormat=\{0:F2\}}"/>
<DataGridTextColumn Header="히트펌프" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn Header="히트펌프" ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Filtering.HeatPumpEnergy}"/> Binding="{Binding Filtering.HeatPumpEnergy, StringFormat=\{0:F2\}}"/>
<DataGridTextColumn Header="에어브로와" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Filtering.AirBlowerEnergy}"/> Binding="{Binding Filtering.AirBlowerEnergy, StringFormat=\{0:F2\}}">
<DataGridTextColumn Header="오존발생기" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn.Header>
Binding="{Binding Sterilizing.OzoneGeneratorEnergy}"/> <StackPanel>
<TextBlock Text="에어" HorizontalAlignment="Center"/>
<TextBlock Text="브로와" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.OzoneGeneratorEnergy, StringFormat=\{0:F2\}}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="오존" HorizontalAlignment="Center"/>
<TextBlock Text="발생기" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.UVSterilizerEnergy, StringFormat=\{0:F2\}}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="자외선" HorizontalAlignment="Center"/>
<TextBlock Text="살균기" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.OzoneDissolverEnergy, StringFormat=\{0:F2\}}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="오존용해" HorizontalAlignment="Center"/>
<TextBlock Text="장치" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.UVSterilizerEnergy}"> Binding="{Binding Sterilizing.ExcessOzoneDestroyerEnergy, StringFormat=\{0:F2\}}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<StackPanel> <StackPanel>
<TextBlock Text="자외선"/> <TextBlock Text="배오존" HorizontalAlignment="Center"/>
<TextBlock Text="살균기"/> <TextBlock Text="장치" HorizontalAlignment="Center"/>
</StackPanel> </StackPanel>
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="오존용해장치" ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.OzoneDissolverEnergy}"/>
<DataGridTextColumn Header="배오존장치" ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.ExcessOzoneDestroyerEnergy}"/>
<DataGridTextColumn Header="총 전력" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn Header="총 전력" ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding TotalEnergy, StringFormat=\{0:F2\}}"/> Binding="{Binding TotalEnergy, StringFormat=\{0:F2\}}"/>
</DataGrid.Columns> </DataGrid.Columns>

@ -41,6 +41,16 @@
SelectedItem="{Binding SelectedFile}" SelectedItem="{Binding SelectedFile}"
ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden"
BorderThickness="0" Background="Transparent"> BorderThickness="0" Background="Transparent">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Margin" Value="2"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="LightSkyBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0 5"> <StackPanel Orientation="Horizontal" Margin="0 5">

@ -97,16 +97,37 @@
Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}" Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Header="모래여과기" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Filtering.SandFilterGreenhouseGas, StringFormat=\{0:F3\}}"/> Binding="{Binding Filtering.SandFilterGreenhouseGas, StringFormat=\{0:F3\}}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="모래" HorizontalAlignment="Center"/>
<TextBlock Text="여과기" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Header="순환펌프" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn Header="순환펌프" ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Filtering.CirculationPumpGreenhouseGas, StringFormat=\{0:F3\}}"/> Binding="{Binding Filtering.CirculationPumpGreenhouseGas, StringFormat=\{0:F3\}}"/>
<DataGridTextColumn Header="히트펌프" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn Header="히트펌프" ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Filtering.HeatPumpGreenhouseGas, StringFormat=\{0:F3\}}"/> Binding="{Binding Filtering.HeatPumpGreenhouseGas, StringFormat=\{0:F3\}}"/>
<DataGridTextColumn Header="에어브로와" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Filtering.AirBlowerGreenhouseGas, StringFormat=\{0:F3\}}"/> Binding="{Binding Filtering.AirBlowerGreenhouseGas, StringFormat=\{0:F3\}}">
<DataGridTextColumn Header="오존발생기" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn.Header>
Binding="{Binding Sterilizing.OzoneGeneratorGreenhouseGas, StringFormat=\{0:F3\}}"/> <StackPanel>
<TextBlock Text="에어" HorizontalAlignment="Center"/>
<TextBlock Text="브로와" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.OzoneGeneratorGreenhouseGas, StringFormat=\{0:F3\}}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="오존" HorizontalAlignment="Center"/>
<TextBlock Text="발생기" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.UVSterilizerGreenhouseGas, StringFormat=\{0:F3\}}"> Binding="{Binding Sterilizing.UVSterilizerGreenhouseGas, StringFormat=\{0:F3\}}">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
@ -116,10 +137,24 @@
</StackPanel> </StackPanel>
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="오존용해장치" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.OzoneDissolverGreenhouseGas, StringFormat=\{0:F3\}}"/> Binding="{Binding Sterilizing.OzoneDissolverGreenhouseGas, StringFormat=\{0:F3\}}">
<DataGridTextColumn Header="배오존장치" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn.Header>
Binding="{Binding Sterilizing.ExcessOzoneDestroyerGreenhouseGas, StringFormat=\{0:F3\}}"/> <StackPanel>
<TextBlock Text="오존용해" HorizontalAlignment="Center"/>
<TextBlock Text="장치" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding Sterilizing.ExcessOzoneDestroyerGreenhouseGas, StringFormat=\{0:F3\}}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="배오존" HorizontalAlignment="Center"/>
<TextBlock Text="장치" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Header="총 배출량" ElementStyle="{StaticResource DataGridElmenetStyle}" <DataGridTextColumn Header="총 배출량" ElementStyle="{StaticResource DataGridElmenetStyle}"
Binding="{Binding TotalGreenhouseGas, StringFormat=\{0:F3\}}"/> Binding="{Binding TotalGreenhouseGas, StringFormat=\{0:F3\}}"/>
</DataGrid.Columns> </DataGrid.Columns>

@ -43,53 +43,136 @@
<DataGrid.Columns> <DataGrid.Columns>
<!-- 측정 시각 --> <!-- 측정 시각 -->
<DataGridTextColumn <DataGridTextColumn
Header="시간" Header="시간" Width="80"
Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}" Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<!-- Tank 값들 --> <!-- Tank 값들 -->
<DataGridTextColumn Header="번호" Binding="{Binding Tanks[0].Number}" <DataGridTextColumn Header="번호" Binding="{Binding Tanks[0].Number}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Header="DO(mg/L)" Binding="{Binding Tanks[0].DOValue, StringFormat=\{0:F2\}}" <DataGridTextColumn Binding="{Binding Tanks[0].DOValue, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="DO" HorizontalAlignment="Center"/>
<TextBlock Text="(mg/L)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Header="pH" Binding="{Binding Tanks[0].PH, StringFormat=\{0:F2\}}" <DataGridTextColumn Header="pH" Binding="{Binding Tanks[0].PH, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Header="ORP(mV)" Binding="{Binding Tanks[0].ORP, StringFormat=\{0:F0\}}" <DataGridTextColumn Binding="{Binding Tanks[0].ORP, StringFormat=\{0:F0\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn Header="온도(℃)" Binding="{Binding Tanks[0].Temperature, StringFormat=\{0:F1\}}" <DataGridTextColumn.Header>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <StackPanel>
<DataGridTextColumn Header="유량(m³/s)" Binding="{Binding Tanks[0].FlowRate, StringFormat=\{0:F2\}}" <TextBlock Text="ORP" HorizontalAlignment="Center"/>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <TextBlock Text="(mV)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[0].Temperature, StringFormat=\{0:F1\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="온도" HorizontalAlignment="Center"/>
<TextBlock Text="(℃)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[0].FlowRate, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="유량" HorizontalAlignment="Center"/>
<TextBlock Text="(m³/s)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="5"/> <DataGridTextColumn Width="5"/>
<DataGridTextColumn Header="번호" Binding="{Binding Tanks[1].Number}" <DataGridTextColumn Header="번호" Binding="{Binding Tanks[1].Number}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Header="DO(mg/L)" 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>
<StackPanel>
<TextBlock Text="DO" HorizontalAlignment="Center"/>
<TextBlock Text="(mg/L)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</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 Header="ORP(mV)" 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="온도(℃)" Binding="{Binding Tanks[1].Temperature, StringFormat=\{0:F1\}}" <DataGridTextColumn.Header>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <StackPanel>
<DataGridTextColumn Header="유량(m³/s)" Binding="{Binding Tanks[1].FlowRate, StringFormat=\{0:F2\}}" <TextBlock Text="ORP" HorizontalAlignment="Center"/>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <TextBlock Text="(mV)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[1].Temperature, StringFormat=\{0:F1\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="온도" HorizontalAlignment="Center"/>
<TextBlock Text="(℃)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[1].FlowRate, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="유량" HorizontalAlignment="Center"/>
<TextBlock Text="(m³/s)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="5"/> <DataGridTextColumn Width="5"/>
<DataGridTextColumn Header="번호" Binding="{Binding Tanks[2].Number}" <DataGridTextColumn Header="번호" Binding="{Binding Tanks[2].Number}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Header="DO(mg/L)" 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>
<StackPanel>
<TextBlock Text="DO" HorizontalAlignment="Center"/>
<TextBlock Text="(mg/L)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</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 Header="ORP(mV)" 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="온도(℃)" Binding="{Binding Tanks[2].Temperature, StringFormat=\{0:F1\}}" <DataGridTextColumn.Header>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <StackPanel>
<DataGridTextColumn Header="유량(m³/s)" Binding="{Binding Tanks[2].FlowRate, StringFormat=\{0:F2\}}" <TextBlock Text="ORP" HorizontalAlignment="Center"/>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <TextBlock Text="(mV)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[2].Temperature, StringFormat=\{0:F1\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="온도" HorizontalAlignment="Center"/>
<TextBlock Text="(℃)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Tanks[2].FlowRate, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="유량" HorizontalAlignment="Center"/>
<TextBlock Text="(m³/s)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
@ -105,30 +188,114 @@
Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}" Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}"/>
<DataGridTextColumn Header="모래여과기 전원" Binding="{Binding Filtering.SandFilterPower, Converter={StaticResource BoolToPowerConverter}}" <DataGridTextColumn Binding="{Binding Filtering.SandFilterPower, Converter={StaticResource BoolToPowerConverter}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn Header="섬프탱크 pH" Binding="{Binding Filtering.SumpPH}" <DataGridTextColumn.Header>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <StackPanel>
<DataGridTextColumn Header="섬프탱크 ORP(mV)" Binding="{Binding Filtering.SumpORP, StringFormat=\{0:F2\}}" <TextBlock Text="모래여과기" HorizontalAlignment="Center"/>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <TextBlock Text="전원" HorizontalAlignment="Center"/>
<DataGridTextColumn Header="섬프탱크 수위(m)" Binding="{Binding Filtering.SumpWaterLevel, StringFormat=\{0:F0\}}" </StackPanel>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> </DataGridTextColumn.Header>
<DataGridTextColumn Header="섬프탱크 유량(m³/s)" Binding="{Binding Filtering.SumpFlowRate, StringFormat=\{0:F1\}}" </DataGridTextColumn>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <DataGridTextColumn Binding="{Binding Filtering.SumpPH}"
<DataGridTextColumn Header="섬프탱크 수온(°C)" Binding="{Binding Filtering.SumpTemperature, StringFormat=\{0:F2\}}" ElementStyle="{StaticResource DataGridElmenetStyle}">
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <DataGridTextColumn.Header>
<DataGridTextColumn Header="순환펌프 전원" Binding="{Binding Filtering.CirculationPumpPower, Converter={StaticResource BoolToPowerConverter}}" <StackPanel>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <TextBlock Text="섬프탱크" HorizontalAlignment="Center"/>
<DataGridTextColumn Header="인버터 제어기 상태" Binding="{Binding Filtering.InverterControllerStatus}" <TextBlock Text="pH" HorizontalAlignment="Center"/>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> </StackPanel>
<DataGridTextColumn Header="순환펌프 유량(m³/s)" Binding="{Binding Filtering.FlowRate, StringFormat=\{0:F2\}}" </DataGridTextColumn.Header>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> </DataGridTextColumn>
<DataGridTextColumn Header="히트펌프 전원" Binding="{Binding Filtering.HeatPumpPower, Converter={StaticResource BoolToPowerConverter}}" <DataGridTextColumn Binding="{Binding Filtering.SumpORP, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}"/> ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn Header="히트펌프 온도(°C)" Binding="{Binding Filtering.HeatPumpTemperature, StringFormat=\{0:F2\}}" <DataGridTextColumn.Header>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <StackPanel>
<DataGridTextColumn Header="에어브로와 전원" Binding="{Binding Filtering.AirBlowerPower, Converter={StaticResource BoolToPowerConverter}}" <TextBlock Text="섬프탱크" HorizontalAlignment="Center"/>
ElementStyle="{StaticResource DataGridElmenetStyle}"/> <TextBlock Text="ORP(mV)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.SumpWaterLevel, StringFormat=\{0:F0\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="섬프탱크" HorizontalAlignment="Center"/>
<TextBlock Text="수위(m)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.SumpFlowRate, StringFormat=\{0:F1\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="섬프탱크" HorizontalAlignment="Center"/>
<TextBlock Text="유량(m³/s)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.SumpTemperature, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="섬프탱크" HorizontalAlignment="Center"/>
<TextBlock Text="수온(°C)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.CirculationPumpPower, Converter={StaticResource BoolToPowerConverter}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="순환펌프" HorizontalAlignment="Center"/>
<TextBlock Text="전원" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.InverterControllerStatus}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="인버터 제어기" HorizontalAlignment="Center"/>
<TextBlock Text="상태" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.FlowRate, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="순환펌프" HorizontalAlignment="Center"/>
<TextBlock Text="유량(m³/s)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.HeatPumpPower, Converter={StaticResource BoolToPowerConverter}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="히트펌프" HorizontalAlignment="Center"/>
<TextBlock Text="전원" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.HeatPumpTemperature, StringFormat=\{0:F2\}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="히트펌프" HorizontalAlignment="Center"/>
<TextBlock Text="온도(°C)" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Filtering.AirBlowerPower, Converter={StaticResource BoolToPowerConverter}}"
ElementStyle="{StaticResource DataGridElmenetStyle}">
<DataGridTextColumn.Header>
<StackPanel>
<TextBlock Text="에어브로와" HorizontalAlignment="Center"/>
<TextBlock Text="전원" HorizontalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>

@ -6,9 +6,12 @@ using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Policy;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading; using System.Windows.Threading;
using FFmpeg.AutoGen; using FFmpeg.AutoGen;
using SmartAquaViewer.Helper.FFHelper; using SmartAquaViewer.Helper.FFHelper;
@ -27,14 +30,16 @@ namespace SmartAquaViewer.ViewModel
set { _currentFrame = value; OnPropertyChanged(); } set { _currentFrame = value; OnPropertyChanged(); }
} }
//private readonly System.Windows.Controls.Image _img;
private readonly object _lockObject = new object(); private readonly object _lockObject = new object();
private Thread _videoThread; private Thread _videoThread;
private Thread _renderingThread; private Thread _renderingThread;
private CancellationTokenSource _videoCancellationTokenSource; private CancellationTokenSource _videoCts;
private CancellationTokenSource _renderingCancellationTokenSource; private CancellationTokenSource _renderCts;
private bool _stopThread = false;
private ConcurrentQueue<Bitmap> _frameQueue = new ConcurrentQueue<Bitmap>(); private readonly Queue<AVFrame> _frameQueue = new(); // lock 불필요
private volatile bool _disposed;
public FFPlayerViewModel(CCTVInfo cctvInfo) public FFPlayerViewModel(CCTVInfo cctvInfo)
{ {
@ -43,172 +48,131 @@ namespace SmartAquaViewer.ViewModel
public void StartMedia(string rtspURL) public void StartMedia(string rtspURL)
{ {
ClosePlayer(); _videoCts = new CancellationTokenSource();
_stopThread = false; _renderCts = new CancellationTokenSource();
_videoThread = new Thread(new ThreadStart(OpenMedia)); _videoThread = new Thread(OpenMedia) { IsBackground = true, Name = "RTSP-Decode" };
_renderingThread = new Thread(new ThreadStart(RenderImage)); _renderingThread = new Thread(RenderImage) { IsBackground = true, Name = "RTSP-Render" };
_videoThread.Priority = ThreadPriority.Highest; // 우선순위 설정
_videoThread.Start(); _videoThread.Start();
_renderingThread.Start(); _renderingThread.Start();
} }
private unsafe void OpenMedia() private unsafe void OpenMedia()
{ {
int failCount = 0;
int frameCount = 0;
_frameQueue = new ConcurrentQueue<Bitmap>();
_videoCancellationTokenSource = new CancellationTokenSource();
try try
{ {
using (StreamDecoder sd = new StreamDecoder(CCTVInfo.RtspUrl)) using (var sd = new StreamDecoder(CCTVInfo.RtspUrl!))
{
using (var vfc = new VideoFrameConverter(sd.FrameSize, sd.PixelFormat, sd.FrameSize, AVPixelFormat.AV_PIX_FMT_BGR24)) using (var vfc = new VideoFrameConverter(sd.FrameSize, sd.PixelFormat, sd.FrameSize, AVPixelFormat.AV_PIX_FMT_BGR24))
{ {
while (!_videoCancellationTokenSource.Token.IsCancellationRequested) while (!_videoCts!.IsCancellationRequested)
{
bool decodeSuccess = sd.TryDecodeNextFrame(out var frame);
//if (!decodeSuccess)
//{
// failCount++;
// HandleDecodeFailure(ref failCount, maxFailCount);
// continue; // 다음 반복으로 이동
//}
// 디코딩 성공 시 프레임 처리
HandleDecodedFrame(frame, vfc, ref frameCount);
failCount = 0; // 실패 카운트 초기화
}
}
}
}
catch (Exception ex)
{
//Log4NetManager.GetLog().Error("OpenMedia() : " + ex.Message);
Debug.WriteLine("OpenMedia() : " + ex.Message.ToString());
}
finally
{ {
if (!_stopThread || !_videoCancellationTokenSource.Token.IsCancellationRequested) if (!sd.TryDecodeNextFrame(out var frame))
{ {
Debug.WriteLine($"Restarting media"); // 디코드 실패: 너무 바쁘지 않게 살짝 쉼
StartMedia(CCTVInfo.RtspUrl); Thread.Sleep(2);
} continue;
}
} }
private void HandleDecodedFrame(AVFrame frame, VideoFrameConverter vfc, ref int frameCount)
{
Bitmap convertedFrame = null;
try try
{ {
convertedFrame = vfc.DeepCopyFrame(frame); AVFrame convertedFrame = vfc.Convert(frame);
_frameQueue.Enqueue(convertedFrame); // 큐 삽입 (길이 제한 적용)
lock (_lockObject) }
catch (Exception ex)
{ {
_frameQueue.Enqueue(convertedFrame); Debug.WriteLine("Decode/Enqueue error: " + ex.Message);
frameCount++; }
}
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
//Log4NetManager.GetLog().Error("HandleDecodedFrame() : " + ex.Message); Debug.WriteLine("OpenMedia() : " + ex);
Debug.WriteLine("HandleDecodedFrame() : " + ex.Message);
convertedFrame?.Dispose();
} }
} }
private void RenderImage() private unsafe void RenderImage()
{ {
int dequeCount = 0;
_renderingCancellationTokenSource = new CancellationTokenSource();
try try
{ {
while (!_renderingCancellationTokenSource.Token.IsCancellationRequested) while (!_renderCts!.IsCancellationRequested)
{ {
Bitmap bitmap = null; if (_frameQueue.Count > 0)
{
AVFrame convertedFrame;
lock (_lockObject) lock (_lockObject)
{ {
if (_frameQueue.Count > 0) if (_frameQueue.Count > 0)
{ {
_frameQueue.TryDequeue(out bitmap); convertedFrame = _frameQueue.Dequeue();
} }
else else
{ {
continue; continue;
} }
} }
if (bitmap == null)
{
Thread.Sleep(10); // CPU 과부하 방지
continue;
}
try try
{ {
//Dispatcher.BeginInvoke((Action)(() => Bitmap bitmap = new Bitmap(convertedFrame.width, convertedFrame.height, convertedFrame.linesize[0], System.Drawing.Imaging.PixelFormat.Format24bppRgb, (IntPtr)convertedFrame.data[0]);
//{
// try var src = CreateBitmapSource(bitmap);
// { src.Freeze();
// DivideAndDisplayBitmap(bitmap);
// } CurrentFrame!.Dispatcher.BeginInvoke(new Action(() =>
// finally
// {
// bitmap.Dispose(); // 자원 해제
// }
//}));
}
catch (ArgumentException ex)
{ {
Console.WriteLine("RenderImage() : " + ex.Message); if (_disposed) return;
CurrentFrame = src;
}), DispatcherPriority.Render);
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("RenderImage() : " + ex.ToString()); Debug.WriteLine("RenderLoop() : " + ex.Message);
} }
_renderingCancellationTokenSource.Token.ThrowIfCancellationRequested();
} }
} }
catch (OperationCanceledException)
{
Console.WriteLine("RenderImage() : Render loop canceled.");
} }
catch (OperationCanceledException) { /* 정상 종료 */ }
finally finally
{ {
// 필요한 자원 정리 //ClearQueue();
//ClearFrameQueue();
} }
} }
public void ClosePlayer() private static BitmapSource CreateBitmapSource(Bitmap bitmap)
{
_stopThread = true;
lock (_lockObject)
{ {
if (_videoCancellationTokenSource != null) // GDI 핸들 사용 X. LockBits → BitmapSource.Create 경로만 사용.
var rect = new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height);
var data = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
try
{ {
_videoCancellationTokenSource.Cancel(); // Format24bppRgb ↔ PixelFormats.Bgr24 매칭
Debug.WriteLine("ClosePlayer(): videoThread 종료함"); return BitmapSource.Create(
data.Width, data.Height, 96, 96,
PixelFormats.Bgr24, null,
data.Scan0, data.Stride * data.Height, data.Stride);
} }
finally
if (_renderingCancellationTokenSource != null)
{ {
_renderingCancellationTokenSource.Cancel(); bitmap.UnlockBits(data);
Debug.WriteLine("ClosePlayer(): RenderingThread 종료함");
} }
} }
public void ClosePlayer()
{
if (_disposed) return;
_disposed = true;
try { _videoCts?.Cancel(); } catch { }
try { _renderCts?.Cancel(); } catch { }
try { if (_videoThread?.IsAlive == true) _videoThread.Join(300); } catch { }
try { if (_renderingThread?.IsAlive == true) _renderingThread.Join(300); } catch { }
_frameQueue.Clear();
_videoCts?.Dispose();
_renderCts?.Dispose();
} }
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;

@ -304,9 +304,17 @@ namespace SmartAquaViewer.ViewModel
{ {
var tbt = new TanksByTime(w.RecordedTime, w.Tanks); var tbt = new TanksByTime(w.RecordedTime, w.Tanks);
TanksByTimes.Add(tbt); TanksByTimes.Add(tbt);
OrderTanksBtTimeByDate();
} }
} }
private void OrderTanksBtTimeByDate()
{
var ordered = TanksByTimes.OrderBy(t => t.RecordedTime).ToList();
TanksByTimes.Clear();
foreach (var t in ordered) TanksByTimes.Add(t);
}
private void DrawGraph(object obj) private void DrawGraph(object obj)
{ {
switch (SelectedGraphType) switch (SelectedGraphType)

Loading…
Cancel
Save