C# WPF,具有受限输入的Datagrid
最近,我在c#-焰火计划软件中启动了一个新项目。我对WinForms很有经验,但我想学习WPF。所以,我已经通过了一些教程,但仍然是在一开始 任务是创建一个数据网格,用户在其中为每个触发点放置数据,例如 Cue、EffectTime、DelayBeforeEffect、FiringTime和Name(未完成) 不,问题是时间(有效时间等)必须由用户通过键盘输入,我想:C# WPF,具有受限输入的Datagrid,c#,wpf,datagrid,user-controls,datagridtemplatecolumn,C#,Wpf,Datagrid,User Controls,Datagridtemplatecolumn,最近,我在c#-焰火计划软件中启动了一个新项目。我对WinForms很有经验,但我想学习WPF。所以,我已经通过了一些教程,但仍然是在一开始 任务是创建一个数据网格,用户在其中为每个触发点放置数据,例如 Cue、EffectTime、DelayBeforeEffect、FiringTime和Name(未完成) 不,问题是时间(有效时间等)必须由用户通过键盘输入,我想: 如果该值类似于sss.fff(简单数或十进制数) 程序会自动将其转换为TimeSpan(h:mm:ss.fff), 这就是我的射
FiringPoint=效应时间。减去(延迟)代码>
UserControl TimeText
,其中只有一个元素-TextBox
,我检查并最终纠正了上面列出的输入点。
然后我在主窗口中创建了DataGrid
和DataGridTemplateColumn
,
类
(具有上面列出的属性)firringpoint
带有FiringPointManager
实现observeCollection
接口INotifyChange
- Manager和DataGrid之间的数据绑定 在面对VisualStudio大约20个小时后,它确实起了作用,但似乎非常复杂。 因为例如,我需要通过dependencyProperty等将DataGridRow.Background color转发到我的TimeText控件
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DataGridHratky
{
/// <summary>
/// Interakční logika pro TimeTextBox.xaml
/// </summary>
public partial class TimeTextBox : UserControl, INotifyPropertyChanged
{
#region Overrides
#endregion
#region Enum
#endregion
#region constants
private const string __NEG = "-";
private static readonly string[] __SEP = { ".", "," };
public const string __TFORMAT = @"%h\:mm\:ss\.fff";
private const string __COL = ":";
#endregion
#region private declarations
private bool _isNegative = false;
private double _onlySecondsValue = 0;
private Brush _okBackground;
private Brush _nokBackground = Brushes.OrangeRed;
private Brush _negBackground = Brushes.LavenderBlush;
private TimeSpan _tsValue = new TimeSpan();
private bool _bOnlySeconds = false;
private string _originalValue;
#endregion
#region private methods
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// Determines if value is only in second format. May be also negative and decimal either with "." or ","
/// </summary>
/// <param name="text">text to check</param>
/// <param name="seconds">Out variable, where seconds should be stored</param>
/// <returns>true if so</returns>
private bool onlySeconds(string text, out double seconds)
{
if (Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator == __SEP[0])
text = text.Replace(__SEP[1], __SEP[0]);
else text = text.Replace(__SEP[0], __SEP[1]);
if (Double.TryParse(text, out seconds))
{
_bOnlySeconds = true;
return true;
} else
{
_bOnlySeconds = false;
return false;
}
}
private bool validate(string time)
{
string _rawValue = time;
if (onlySeconds(_rawValue, out _onlySecondsValue))
{
return true;
}
//check if it is negative
if (_rawValue.StartsWith(__NEG))
{
_isNegative = true;
}
else _isNegative = false;
if (TimeSpan.TryParse(_rawValue, out _tsValue)) return true;
else return false;
}
/// <summary>
/// Determines time based on:
/// - if there is only one : in string means minutes and seconds
/// - if there are two : means hours and minutes and seconds
/// - in both cases milliseconds after . or , may be present
/// </summary>
/// <param name="sTime">String representing validated time</param>
/// <returns>Time span with translated time</returns>
private TimeSpan determineTime(string sTime)
{
int _iColon = Regex.Matches(sTime,__COL).Count;
string _sResult;
TimeSpan _tsDays = new TimeSpan();
if (TimeSpan.TryParse(sTime, out _tsDays))
{
if (_tsDays.Days > 0)
return new TimeSpan(0, _tsDays.Hours, _tsDays.Minutes, _tsDays.Seconds, _tsDays.Milliseconds);
}
TimeSpan _ts = new TimeSpan();
if (_iColon == 1) //minutes and seconds
{
//add 0 hours and 0 days
_sResult = addTimeToTimeSpan(sTime, "0.0:");
}
else if
(_iColon == 2) //hours minutes and seconds
{
//add 0 days
_sResult = addTimeToTimeSpan(sTime, "0.");
}
else _sResult = sTime;
if (TimeSpan.TryParse(_sResult, out _ts))
{
// in all cases remove day
return new TimeSpan(0, _ts.Hours, _ts.Minutes, _ts.Seconds, _ts.Milliseconds);
}
else return _ts;
}
/// <summary>
/// Returns time with added value, moves __NEG sign if present to the from
/// </summary>
/// <param name="sTime">Original time</param>
/// <param name="sTimeToAdd">Time to add</param>
/// <returns>string with complete time</returns>
private string addTimeToTimeSpan(string sTime, string sTimeToAdd)
{
string _sResult;
if (sTime.StartsWith(__NEG))
{
_sResult = __NEG + sTimeToAdd + sTime.Remove(0, 1);
}
else _sResult = sTimeToAdd + sTime;
return _sResult;
}
#endregion
#region Public events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region delegates
#endregion
#region Public delcarations
#region Dependency
public Brush BKColor
{
get { return (Brush)GetValue(BKColorProperty); }
set { SetValue(BKColorProperty, value); }
}
// Using a DependencyProperty as the backing store for BKColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BKColorProperty =
DependencyProperty.Register("BKColor", typeof(Brush), typeof(TimeTextBox), new PropertyMetadata(null));
public bool ReadOnly
{
get { return (bool)GetValue(ReadOnlyProperty); }
set {
SetValue(ReadOnlyProperty, value);
timeTextBox.ReadOnly = value;
}
}
// Using a DependencyProperty as the backing store for ReadOnly. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ReadOnlyProperty =
DependencyProperty.Register("ReadOnly", typeof(bool), typeof(TimeTextBox), new PropertyMetadata(null));
public string TimeText
{
get { return (string)GetValue(TimeTextProperty); }
set {
SetValue(TimeTextProperty, value);
//OnPropertyChanged("TimeText");
}
}
public static readonly DependencyProperty TimeTextProperty =
DependencyProperty.Register("TimeText", typeof(string), typeof(TimeTextBox),
new PropertyMetadata(string.Empty, OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
TimeTextBox myUserControl = dependencyObject as TimeTextBox;
myUserControl.OnPropertyChanged("TimeText");
myUserControl.OnTextPropertyChanged(e);
}
private void OnTextPropertyChanged(DependencyPropertyChangedEventArgs e)
{
txtTime.Text = TimeText;
}
public string Time
{
get { return txtTime.Text; }
set { txtTime.Text = value; }
}
#endregion
/// <summary>
/// True if time span is negative
/// </summary>
public bool IsNegative
{
get
{
return _isNegative;
}
}
/// <summary>
/// Gets or sets validated value background color
/// </summary>
public Brush ValueOKBackground
{
get
{
return _okBackground;
}
set
{
_okBackground = value;
}
}
/// <summary>
/// Gets or sets background color if value is not succesfully validated
/// </summary>
public Brush ValueNotOKBackground
{
get
{
return _okBackground;
}
set
{
_nokBackground = value;
}
}
#endregion
#region Public methods
#endregion
#region Constructors
public TimeTextBox()
{
InitializeComponent();
_okBackground = txtTime.Background;
}
#endregion
private void txtTime_TextChanged(object sender, TextChangedEventArgs e)
{
if (validate(txtTime.Text)) txtTime.Background = BKColor;
else
txtTime.Background = _nokBackground;
}
private void txtTime_LostFocus(object sender, RoutedEventArgs e)
{
//leave if nothing changed
if (txtTime.Text == _originalValue) return;
if (validate(txtTime.Text))
{
if (_bOnlySeconds)
{
int seconds = (int)Math.Truncate(_onlySecondsValue);
int milliseconds = (int)((_onlySecondsValue - seconds) * 1000);
_tsValue = new TimeSpan(0, 0, 0, seconds, milliseconds);
}
else
{
_tsValue = determineTime(txtTime.Text);
}
}
else
{
if (!validate(_originalValue)) //validate methods uses _tsValue to put validated timespan
{
_tsValue = new TimeSpan();
}
}
string _sign = _isNegative ? "-" : "";
txtTime.Text = _sign + _tsValue.ToString(__TFORMAT);
txtTime.Background = _isNegative ? _negBackground : BKColor;
TimeText = txtTime.Text;
txtTime.Background = BKColor;
OnPropertyChanged("UpdateTime");
}
private void txtTime_GotFocus(object sender, RoutedEventArgs e)
{
_originalValue = txtTime.Text;
if (!(validate(txtTime.Text))) txtTime.Text = "";
}
}
}
使用系统;
使用System.Collections.Generic;
使用系统组件模型;
使用System.Linq;
使用系统文本;
使用System.Text.RegularExpressions;
使用系统线程;
使用System.Threading.Tasks;
使用System.Windows;
使用System.Windows.Controls;
使用System.Windows.Data;
使用System.Windows.Documents;
使用System.Windows.Input;
使用System.Windows.Media;
使用System.Windows.Media.Imaging;
使用System.Windows.Navigation;
使用System.Windows.Shapes;
命名空间DataGridHratky
{
///
///Interakčnílogika pro TimeTextBox.xaml
///
公共部分类时间文本框:UserControl,INotifyPropertyChanged
{
#区域覆盖
#端区
#区域枚举
#端区
#区域常数
私有常量字符串_NEG=“-”;
私有静态只读字符串[]uu SEP={“.”,“};
公共常量字符串格式=@“%h \:mm \:ss\.fff”;
private const string__COL=“:”;
#端区
#区域私人声明
私有布尔值为负=假;
私有双精度_onlySecondsValue=0;
私人画笔背景;
私人画笔_nokBackground=画笔为橙色;
私人画笔_negBackground=画笔.LavenderBlush;
私有时间跨度_tsValue=new TimeSpan();
private bool_bOnlySeconds=false;
私有字符串_originalValue;
#端区
#区域私有方法
私有void OnPropertyChanged(字符串名称)
{
PropertyChangedEventHandler处理程序=PropertyChanged;
if(处理程序!=null)
{
处理程序(此,新PropertyChangedEventArgs(名称));
}
}
///
///确定值是否仅为第二种格式。也可以是负数和带“.”或“,”的十进制数
///
///要检查的文本
///Out变量,其中应存储秒数
///如果是,则为真
private bool only秒(字符串文本,输出双秒)
{
如果(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator==\uu SEP[0])
text=text.Replace(uu SEP[1],u SEP[0]);
else text=text.Replace(u SEP[0],u SEP[1]);
if(Double.TryParse(文本,输出秒))
{
_bOnlySeconds=true;
返回true;
}否则
{
_bOnlySeconds=false;
返回false;
}
}
私有布尔验证(字符串时间)
{
字符串_rawValue=时间;
if(仅秒(_rawValue,out _onlySecondsValue))
{
返回true;
}
//检查是否为阴性
如果(_rawValue.StartsWith(_NEG))
{
_isNegative=true;
}
否则为阴性=假;
if(TimeSpan.TryParse(_rawValue,out _tsValue))返回true;
否则返回false;
}
///
///根据以下内容确定时间:
///-如果只有一个:在字符串中表示分和秒
///-如果有两个:表示小时、分和秒
///-在这两种情况下,毫秒后。或,可能存在
///
///表示已验证时间的字符串
///时间跨度与转换时间
专用时间跨度确定时间(字符串时间)
{
int _iColon=Regex.Matches(sTime,u COL).Count;
字符串_sResult;
TimeSpan _tsDays=新的TimeSpan();
if(时间跨度(时间,外出天数))
{
如果(tsDays.Days
<UserControl x:Name="timeTextBox" x:Class="DataGridHratky.TimeTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DataGridHratky"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="100">
<!-- DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TimeTextBox}}-->
<Grid x:Name="grdLayout">
<TextBox x:Name="txtTime" TextChanged="txtTime_TextChanged" LostFocus="txtTime_LostFocus" GotFocus="txtTime_GotFocus" BorderThickness="0"
Text="{Binding Path=TimeText, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TimeTextBox}}}"
IsReadOnly="{Binding Path=ReadOnly, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TimeTextBox}}}"
Background="{Binding Path=BKColor, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TimeTextBox}}}"
VerticalContentAlignment="Center"/>
</Grid>
</UserControl>
<Window x:Class="DataGridHratky.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataGridHratky"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<DataGrid x:Name="dgData" AutoGenerateColumns="False" AddingNewItem="dgData_AddingNewItem" BeginningEdit="dgData_BeginningEdit" CellEditEnding="dgData_CellEditEnding" RowHeight="25" AlternationCount="2" VerticalContentAlignment="Center" >
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Background" Value="White" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Background" Value="WhiteSmoke" />
</Trigger>
<DataTrigger Binding="{Binding Path=Selectable}" Value="False">
<DataTrigger.Setters>
<Setter Property="Background" Value="White" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridComboBoxColumn x:Name="colCue" SelectedItemBinding="{Binding SelectedCue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="{Binding AvaiableCues}" Width="50" Header="Cue"/>
<DataGridTextColumn x:Name="colCaption" Width="200" Header="Popis" Binding="{Binding Caption}" />
<DataGridTemplateColumn ClipboardContentBinding="{x:Null}" Header="Čas efektu" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TimeTextBox x:Name="ttbFireEffect" TimeText="{Binding Effect, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
BKColor="{Binding Path=Background, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridComboBoxColumn x:Name="colType" SelectedItemBinding="{Binding SelectedType, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="{Binding AvaiableFWTypes}" Header="Typ" Width="75" />
<DataGridTemplateColumn ClipboardContentBinding="{x:Null}" Header="Prodleni">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TimeTextBox x:Name="ttbDelay" TimeText="{Binding Delay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ReadOnly = "{Binding IsDelayReadOnly}"
BKColor="{Binding Path=Background, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn ClipboardContentBinding="{x:Null}" Header="Čas odpalu" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TimeTextBox x:Name="ttbFirePoint" TimeText="{Binding FiringPoint, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
BKColor="{Binding Path=Background, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataGridHratky
{
public class FirepointsManager : INotifyPropertyChanged
{
#region Enum
#endregion
#region constants
#endregion
#region private declarations
private List<string> _avaiableFirepoints = new List<string>();
private List<string> _avaiableFireworks = new List<string>();
#endregion
#region private methods
#endregion
#region Public events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Protected
protected void RaiseChange(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
#endregion
#region delegates
#endregion
#region Public properties
/// <summary>
/// Firepoints Observable collection
/// </summary>
public ObservableCollection<FirePoint> FirePoints { get; set; }
/// <summary>
/// List of avaiable firepoints
/// </summary>
public List<string>AvaiableCues
{
get
{
return _avaiableFirepoints;
}
}
/// <summary>
/// List of pyrotechnics types
/// </summary>
public List<string>AvaiableFWTypes
{
get
{
return _avaiableFireworks;
}
}
#endregion
#region Public methods
/// <summary>
/// Adds a new Firepoint
/// </summary>
/// <param name="f">Firepoint</param>
public void AddFirePoint(FirePoint f)
{
f.SelectedCueChanged += F_SelectedCueChanged;
FirePoints.Add(f);
}
/// <summary>
/// Checks avaible cues and eliminates __MS if already used
/// </summary>
public void CheckSelectedCues()
{
foreach (var FirePoint in FirePoints)
{
if (FirePoint.SelectedCue == FirePoint.__MS)
{
if (_avaiableFirepoints.Contains(FirePoint.__MS))
_avaiableFirepoints.Remove(FirePoint.__MS);
return;
}
}
if (!_avaiableFirepoints.Contains(FirePoint.__MS))
_avaiableFirepoints.Add(FirePoint.__MS);
RaiseChange("CuesAvaiable");
}
private void F_SelectedCueChanged(object sender, EventArgs e)
{
CheckSelectedCues();
}
#endregion
#region Constructors
public FirepointsManager()
{
FirePoints = new ObservableCollection<FirePoint>();
FirePoints.CollectionChanged += FirePoints_CollectionChanged;
_avaiableFirepoints = FirePoint.GeneratedCues();
_avaiableFireworks = FirePoint.FireworksTypes();
}
private void FirePoints_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
CheckSelectedCues();
}
#endregion
}
}