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

@ -6,7 +6,7 @@
xmlns:local="clr-namespace:SmartAquaViewer.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Border Margin="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="1">
<Border>
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="24"></RowDefinition>
@ -24,25 +24,22 @@
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Name="lblVideoStatus" Text="연결 없음"
FontSize="17" Foreground="#414961"></TextBlock>
</Grid>
</Border>
<Image x:Name="imgPlayer" Grid.RowSpan="2" Stretch="Fill"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Source="{Binding CurrentFrame}"/>
<Label HorizontalAlignment="Left" Grid.RowSpan="2" Margin="20, 5, 5, 0" x:Name="lblCCTVID"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<TextBlock HorizontalAlignment="Left" Grid.RowSpan="2" Margin="20, 5, 5, 0" x:Name="lblCCTVID"
FontFamily="Verna" FontSize="15" FontWeight='Bold' Foreground="White"
Content="{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>
Text="{Binding CCTVInfo.DeviceId}"/>
<!--<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"/>
</StackPanel>-->
<Grid Grid.Row="1">
<StackPanel VerticalAlignment="Bottom" Orientation="Vertical">
<Label HorizontalAlignment="Right" Margin="20, 5, 5, 0" x:Name="lblCCTVName"
FontFamily="Verna" FontSize="15" FontWeight='Bold' Foreground="White"
Content="{Binding CCTVInfo.DeviceName}"/>
<TextBlock Text="{Binding CCTVInfo.DeviceName}" HorizontalAlignment="Right" Margin="20, 5, 5, 0" x:Name="lblCCTVName"
FontFamily="{StaticResource SCDream6}" FontSize="15" Foreground="White"/>
</StackPanel>
</Grid>
</Grid>

@ -37,7 +37,7 @@
<Label Name="lblVideoStatus" Content="연결 없음" Grid.Row="0" FontSize="17" Foreground="#414961" HorizontalContentAlignment="Center"></Label>
</Grid>
</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}"/>
<TextBlock Text="{Binding DeviceId}" Margin="20 5 5 0" x:Name="lblCCTVId"
FontFamily="{StaticResource SCDream6}" FontSize="15" Foreground="White"/>

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

@ -41,6 +41,16 @@
SelectedItem="{Binding SelectedFile}"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
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>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0 5">

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

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

@ -6,9 +6,12 @@ using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using FFmpeg.AutoGen;
using SmartAquaViewer.Helper.FFHelper;
@ -27,14 +30,16 @@ namespace SmartAquaViewer.ViewModel
set { _currentFrame = value; OnPropertyChanged(); }
}
//private readonly System.Windows.Controls.Image _img;
private readonly object _lockObject = new object();
private Thread _videoThread;
private Thread _renderingThread;
private CancellationTokenSource _videoCancellationTokenSource;
private CancellationTokenSource _renderingCancellationTokenSource;
private bool _stopThread = false;
private CancellationTokenSource _videoCts;
private CancellationTokenSource _renderCts;
private ConcurrentQueue<Bitmap> _frameQueue = new ConcurrentQueue<Bitmap>();
private readonly Queue<AVFrame> _frameQueue = new(); // lock 불필요
private volatile bool _disposed;
public FFPlayerViewModel(CCTVInfo cctvInfo)
{
@ -43,172 +48,131 @@ namespace SmartAquaViewer.ViewModel
public void StartMedia(string rtspURL)
{
ClosePlayer();
_stopThread = false;
_videoCts = new CancellationTokenSource();
_renderCts = new CancellationTokenSource();
_videoThread = new Thread(new ThreadStart(OpenMedia));
_renderingThread = new Thread(new ThreadStart(RenderImage));
_videoThread = new Thread(OpenMedia) { IsBackground = true, Name = "RTSP-Decode" };
_renderingThread = new Thread(RenderImage) { IsBackground = true, Name = "RTSP-Render" };
_videoThread.Priority = ThreadPriority.Highest; // 우선순위 설정
_videoThread.Start();
_renderingThread.Start();
}
private unsafe void OpenMedia()
{
int failCount = 0;
int frameCount = 0;
_frameQueue = new ConcurrentQueue<Bitmap>();
_videoCancellationTokenSource = new CancellationTokenSource();
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))
{
while (!_videoCancellationTokenSource.Token.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
while (!_videoCts!.IsCancellationRequested)
{
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
{
convertedFrame = vfc.DeepCopyFrame(frame);
lock (_lockObject)
AVFrame convertedFrame = vfc.Convert(frame);
_frameQueue.Enqueue(convertedFrame); // 큐 삽입 (길이 제한 적용)
}
catch (Exception ex)
{
_frameQueue.Enqueue(convertedFrame);
frameCount++;
Debug.WriteLine("Decode/Enqueue error: " + ex.Message);
}
}
}
}
catch (Exception ex)
{
//Log4NetManager.GetLog().Error("HandleDecodedFrame() : " + ex.Message);
Debug.WriteLine("HandleDecodedFrame() : " + ex.Message);
convertedFrame?.Dispose();
Debug.WriteLine("OpenMedia() : " + ex);
}
}
private void RenderImage()
private unsafe void RenderImage()
{
int dequeCount = 0;
_renderingCancellationTokenSource = new CancellationTokenSource();
try
{
while (!_renderingCancellationTokenSource.Token.IsCancellationRequested)
while (!_renderCts!.IsCancellationRequested)
{
Bitmap bitmap = null;
if (_frameQueue.Count > 0)
{
AVFrame convertedFrame;
lock (_lockObject)
{
if (_frameQueue.Count > 0)
{
_frameQueue.TryDequeue(out bitmap);
convertedFrame = _frameQueue.Dequeue();
}
else
{
continue;
}
}
if (bitmap == null)
{
Thread.Sleep(10); // CPU 과부하 방지
continue;
}
try
{
//Dispatcher.BeginInvoke((Action)(() =>
//{
// try
// {
// DivideAndDisplayBitmap(bitmap);
// }
// finally
// {
// bitmap.Dispose(); // 자원 해제
// }
//}));
}
catch (ArgumentException ex)
Bitmap bitmap = new Bitmap(convertedFrame.width, convertedFrame.height, convertedFrame.linesize[0], System.Drawing.Imaging.PixelFormat.Format24bppRgb, (IntPtr)convertedFrame.data[0]);
var src = CreateBitmapSource(bitmap);
src.Freeze();
CurrentFrame!.Dispatcher.BeginInvoke(new Action(() =>
{
Console.WriteLine("RenderImage() : " + ex.Message);
if (_disposed) return;
CurrentFrame = src;
}), DispatcherPriority.Render);
}
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
{
// 필요한 자원 정리
//ClearFrameQueue();
//ClearQueue();
}
}
public void ClosePlayer()
{
_stopThread = true;
lock (_lockObject)
private static BitmapSource CreateBitmapSource(Bitmap bitmap)
{
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();
Debug.WriteLine("ClosePlayer(): videoThread 종료함");
// Format24bppRgb ↔ PixelFormats.Bgr24 매칭
return BitmapSource.Create(
data.Width, data.Height, 96, 96,
PixelFormats.Bgr24, null,
data.Scan0, data.Stride * data.Height, data.Stride);
}
if (_renderingCancellationTokenSource != null)
finally
{
_renderingCancellationTokenSource.Cancel();
Debug.WriteLine("ClosePlayer(): RenderingThread 종료함");
bitmap.UnlockBits(data);
}
}
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;

@ -304,9 +304,17 @@ namespace SmartAquaViewer.ViewModel
{
var tbt = new TanksByTime(w.RecordedTime, w.Tanks);
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)
{
switch (SelectedGraphType)

Loading…
Cancel
Save