首页 > 解决方案 > WPF,输入受限的Datagrid

问题描述

最近我开始了一个 c# 的新项目——Fireworks 规划软件。我在 WinForms 方面很有经验,但我想学习 WPF。所以我已经通过了几个教程,但还处于起步阶段。

任务是创建一个数据网格,用户在其中放置每个 Firing 点的数据,例如 Cue、EffectTime、DelayBeforeEffect、FiringTime 和 Name(它不完整)。主窗口预览

不,问题是用户必须通过键盘输入时间(EffectTime 等),我想:

  1. 如果该值类似于 sss.fff(简单数字或十进制数字),程序会自动将其转换为 TimeSpan (h:mm:ss.fff),这实际上是我的射击系统的分辨率。示例:数字 65.528 转换为 0:01:05.528。
  2. 负值也是允许的,因为在主要烟火表演开始之前有几件事要做
  3. 我想验证用户输入它是有效的时间值,而不是例如 65.528.20。如果值不正确,我需要取消用户离开编辑的单元格或至少用最后验证的值重写新值
  4. 值之间存在一些关系,例如:FiringPoint = EffectTime - Delay,或更好FiringPoint = EffectTime.Subtract(Delay);

正如我习惯的那样,我创建了一个UserControl TimeText只有一个元素的 -TextBox我检查并最终正确输入了上面列出的点。DataGrid然后我DataGridTemplateColumn在MainWindow中创建了类似的类

所以最后我的问题是:我的方法是否正确,或者有人可以建议更好的方法来达到相同的功能吗?

感谢您的意见一月

TimeTextBox 的代码

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 = "";
        }
    }
}

时间文本 XAML

<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>

主窗口 XAML

<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>

FirepointManager 代码

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
    }
}

标签: c#wpfdatagriduser-controlsdatagridtemplatecolumn

解决方案


推荐阅读