c# 零碎笔记

1.WPF ListViewItem触发双击事件的正确处理方式:

<ListView.ItemContainerStyle>
   		<Style TargetType="{x:Type ListViewItem}">
            <Setter Property="IsSelected" Value="{Binding IsSelected,Mode=TwoWay}" />
            <EventSetter Event="MouseDoubleClick" Handler="Button_MouseDoubleClick"></EventSetter>
         </Style>
</ListView.ItemContainerStyle>

2.寻找某一个控件的模板中的子控件:

 public static ChildItem FindVisualChild<ChildItem>(DependencyObject obj) where ChildItem : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is ChildItem)
                 	return (ChildItem)child;
                else
                {
                    ChildItem childOfChild = FindVisualChild<ChildItem>(child);
                    if (childOfChild != null)
                        return childOfChild;
                }
            }
            return null;
        }

例如寻找一个listbox中的scrollviewer:

 ScrollViewer s = FindVisualChild<ScrollViewer>(this.listbox1);

3.WPF提示找不到http://schemas.microsoft.com/expression/2010/drawing

这个blend里的东西,添加对Microsoft.Expression.Drawing的引用
参考:http://www.myexception.cn/c-sharp/1456925.html

4.处理BeginInvoke的异常

场景:比如你写了一个框架,里面有个记录日志的委托Log,在自己框架是异步调用的,不想别人的实现占用框架执行其他方法的时间,需要别人去实现方法怎么记录日志,当别人的实现方法有异常时,可能会使你的框架崩溃,这时候的问题是:如何捕获BeginInvoke的异常?
答案是,BeginInvoke后面有个Callback函数,在那里可以捕获异常
且看下面的Demo:

public class Test
    {
        /// 
        /// 模拟框架的Log日志
        /// 
        public  Action<string> Log;
        public void TestLog(string msg)
        {
            Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}]:enter testlog,msg:{msg}");
            Log.BeginInvoke(msg,Callback,null);
            Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}]:exit testlog,msg:{msg}");
        }

        private void Callback(IAsyncResult ar)
        {
            //捕获BeginInvoke的异常
            try
            {
                
                Log.EndInvoke(ar);
            }
            catch (Exception e)
            {
                Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}]:Callback异常,{e.Message}");
            }
        }
    }

调用:

class Program
    {
        static void Main(string[] args)
        {
            Test test = new Test();
            test.Log += TestMethod;
            List<Task> tasks = new List<Task>();
            for (int i = 0; i < 5; i++)
            {
                Task task = Task.Factory.StartNew(x =>
                  {
                      int j = (int)x;
                      test.TestLog(j.ToString());
                  }, i);
                tasks.Add(task);
            }

            Task.WaitAll(tasks.ToArray());
            
            Console.Read();
        }
        static void TestMethod(string msg)
        {
            Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}]:TestMethod,enter...");
            Thread.Sleep(5000);
            //模拟异常
            int i = 0;
            int j = 10 / i;
            Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}]:TestMethod,msg:{msg}");
        }
    }

结果:
c# 零碎笔记_第1张图片
异常都被捕获到了。
这个场景之前的处理方式是启动一个Task,在Task中用Try…catch,因为日志记录过多,且是多线程操作,每次启动Task感觉开销过大,所以就用BeginInvoke

5.WCF死锁

使用Winform或者WPF进行双工通讯时,可能有死锁发生。解决办法:
https://www.cnblogs.com/artech/archive/2007/03/29/692032.html

6.默认参数问题

 class Program
    {
        static void Main(string[] args)
        {
            ITest test = new Test();
            Test test1 = new Test();
            test.Show();
            test1.Show();
            Console.Read();
        }
    }
    public interface ITest
    {
        void Show(int a=100);
    }
    public class Test : ITest
    {
        public void Show(int a = 500)
        {
            Console.WriteLine(a);
        }
    }

输出结果:
c# 零碎笔记_第2张图片

7.WPF继承默认样式

平时我们定义了全局样式(没有key的),有时候需要继承这个全局,再增加点东西,可以用下面的方法

<Style TargetType="{x:Type StackPanel}" BasedOn="{StaticResource {x:Type StackPanel}}">
  <!-- ... -->
</Style>

8.WPF设置按钮左键点击弹出菜单

 /// 
    /// 设置左键单击弹出菜单的按钮
    /// 
    public class ButtonEx:System.Windows.Controls.Button
    {
        private ContextMenu menu;
        public ButtonEx()
        {
            this.Loaded += ButtonEx_Loaded;
            this.Click += ButtonEx_Click;
        }

        private void ButtonEx_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            this.menu.PlacementTarget = this;
            this.menu.Placement = System.Windows.Controls.Primitives.PlacementMode.RelativePoint;
            this.menu.HorizontalOffset = 0;
            this.menu.VerticalOffset = this.ActualHeight;
            this.menu.IsOpen = true;
        }

        private void ButtonEx_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (this.ContextMenu != null)
            {
                this.menu = ContextMenu;
                this.ContextMenu = null;
            }
        }
    }

9.DB2中对空值的处理

SELECT memtyp_id as ID, display_name AS DisplayName, COALESCE(type_description,'NULL') as type_description from test

COALESCE(type_description,'NULL')就是type_description为空时,返回“NULL”字符串的意思
ORACLE中相应的函数是NVL,MySql中相应的函数是IFNULL

10.WPF中自定义窗体(Window)

WPF中,Win10下系统自带的窗体是这样的:
c# 零碎笔记_第3张图片
在Win7下可能更难看。有时候我们需要在最上方的标题栏上加一些东西或者自定义标题栏,一般的做法是:设置WindowStyle="None"然后自己加一个标题栏上去,但是这样做的话会屏蔽掉Window很多自带的功能(例如双击标题栏最大化,调整Window的大小等),所以不建议这样做。
我想说的是另一种方法:
设置如下附加属性,也能达到效果,而且完美的不损失Window原有的功能:

 <Window.Style>
        <Style TargetType="Window" BasedOn="{StaticResource {x:Type Window}}">
            <Setter Property="WindowChrome.WindowChrome">
                <Setter.Value>
                    <WindowChrome CornerRadius="0"
                      GlassFrameThickness="1"
                      UseAeroCaptionButtons="False"
                      NonClientFrameEdges="None" />
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Style>

这样设置之后,展现出来的窗体的标题栏就不见了,但是完美的继承了Window的所有功能,这样我们再自定义标题栏就可以
参考:https://www.cnblogs.com/dino623/p/CustomWindowStyle.html
https://www.cnblogs.com/huaxia283611/p/wpf-maximize-windowchrome.html

11.WPF 将控件的事件绑定到后台的命令

例如将ComboBox 的选择改变事件绑定到ViewModel的SelectChangedCmd:

<ComboBox >
	<i:Interaction.Triggers>
	   <i:EventTrigger EventName="SelectionChanged">
	       <i:InvokeCommandAction Command="{Binding SelectChangedCmd}" />
	   </i:EventTrigger>
	</i:Interaction.Triggers>
</ComboBox>

引用System.Windows.Interactivity.dll后,需要引入:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

12 WPF中使用Observerable集合过滤

var view= System.Windows.Data.CollectionViewSource.GetDefaultView(this.FileList);
view.Filter=...

这样,先获取ObserverableCollection的view,然后设置view的Filter即可实现过滤

13 WPF DataGrid实现显示行号:

首先建一个Model,里面包含Index属性,让Index与DataGrid的一列绑定:

<DataGrid Grid.Row="2" x:Name="dataGrid" ItemsSource="{Binding ConfigList}" Margin="2" RowHeaderWidth="80">
    <DataGrid.Columns>
        <DataGridTextColumn Header="序号" Width="80" Binding="{Binding Index}"></DataGridTextColumn>                    
    </DataGrid.Columns>
</DataGrid>

处理DataGrid的RowLoading事件:

 this.dataGrid.LoadingRow += DataGrid_LoadingRow;
 private void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
 {
     var model = e.Row.DataContext as HtmlSwitchConfigEditModel;
     if (model != null)
     {
         model.Index = e.Row.GetIndex();
     }
 }

14 MVVM实现监控键盘事件

<TextBox x:Name="tbTrayCode" Width="150" Height="30" langtian:TextBoxHelper.Watermark="等待扫码..." IsReadOnly="{Binding ElementName=cbManualScanBatCode,Path=IsChecked,Converter={StaticResource BoolInverseConverter}}" Text="{Binding TrayCode,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
     <TextBox.InputBindings>
         <KeyBinding Key="Enter" Command="{Binding TrayCodeCompleteInputCmd}"></KeyBinding>
     </TextBox.InputBindings>
 </TextBox>

15 FreeSql事务操作Sqlite死锁问题

public int Init(WorkInfo workInfo, IEnumerable<BatRecord> batRecoreds)
{
    using (var ctx = freeSql.CreateDbContext())
    {
        ctx.Orm.Insert(workInfo).ExecuteAffrows();
        ctx.Orm.Insert(batRecoreds).ExecuteAffrows();
        return ctx.SaveChanges();
    }
}

这样操作不同的表使用事务会报数据库被锁住

public int Init(WorkInfo workInfo, IEnumerable<BatRecord> batRecoreds)
{
     using (var ctx = freeSql.CreateDbContext())
     {
         ctx.Orm.Insert(workInfo).ExecuteAffrows();
         return ctx.SaveChanges();
     }
     using (var ctx = freeSql.CreateDbContext())
     {
         ctx.Orm.Insert(batRecoreds).ExecuteAffrows();
         ctx.SaveChanges();
     }
}

这样操作不会报死锁

16 DataGridHeader的隐藏绑定

<Grid x:Name="gridMask"></Grid>
 <DataGridTextColumn Header="条码" Visibility="{Binding DataContext.ColumnConfig.IsBatCodeShow,Source= {x:Reference gridMask},Converter={StaticResource BoolToVisibleConverter}}"
                                                Binding="{Binding BatCode}"></DataGridTextColumn>

1.使用ElementName和RelativeSource均不行,只有使用Source= {x:Reference ...}才有用!!!
2.gridMask必须是DataGrid之后的元素,否则会报循环依赖的错误!!!!

17 比较两个路径是否一样

static void Main(string[] args)
        {
            string path1 = "c:\\a\\b\\a.txt";
            string path2 = "c:\\a\\b/a.txt";
            string path3 = "c:/a/b/a.txt";
            string path4 = "c:/a/b\\a.txt";

            Uri uri1 = new Uri(path1);
            Uri uri2 = new Uri(path2);
            Uri uri3 = new Uri(path3);
            Uri uri4 = new Uri(path4);

            Console.WriteLine(uri1==uri2);
            Console.WriteLine(uri1==uri3);
            Console.WriteLine(uri1==uri4);
            Console.Read();
        }

c# 零碎笔记_第4张图片

18 枚举与Int的转换

 public enum Status
 {
      On,
      Off
  }
 var v= Enum.Parse(typeof(Status), "1");
 var v1 = Enum.Parse(typeof(Status), "Off");
 Console.WriteLine(v.Equals(v1));
 Console.Read();

c# 零碎笔记_第5张图片

19 WPF xaml使用枚举作为ItemSource

<ObjectDataProvider x:Key="FlowTypes" MethodName="GetValues" ObjectType="{x:Type flowDataModel:FlowStepType}">
    <ObjectDataProvider.MethodParameters>
        <x:Type Type="flowDataModel:FlowStepType"/>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<ComboBox Width="100" Height="30" ItemsSource="{Binding Source={StaticResource FlowTypes}}" SelectedItem="{Binding SelectedFlowType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

20 radioButton与枚举绑定的转换器

public class EnumToBooleanConverter : IValueConverter
 {
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
      {
          return value == null ? false : value.Equals(parameter);
      }

      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
      {
          return value != null && value.Equals(true) ? parameter : Binding.DoNothing;
      }
  }
<StackPanel>
     <StackPanel Orientation="Horizontal" Margin="2">
         <RadioButton Content="手动发送" GroupName="11" IsChecked="{Binding FlowStartType,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,Converter={StaticResource EnumToBooleanConverter},ConverterParameter={x:Static config:FlowStartType.ManualStart}}"></RadioButton>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Margin="2">
         <RadioButton Content="扫码发送" GroupName="11" IsChecked="{Binding FlowStartType,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,Converter={StaticResource EnumToBooleanConverter},ConverterParameter={x:Static config:FlowStartType.ScanStart}}"></RadioButton>
         <TextBlock Text="压床码+托盘码+确认码" Foreground="Gray" Margin="10,0,0,0" FontSize="10" VerticalAlignment="Bottom"></TextBlock>
     </StackPanel>
 </StackPanel>

注意:此处的RadioButton 不能在同级目录下,因为同级目录下的RatioButton本身具有互斥性,会导致绑定失败!!!

21 TextBox在MVVM模式下绑定double类型无法输入小数点的解决方法:

在启动程序时,加入如下代码:

//解决TextBox MVVM模式绑定时,不能输入小数点的问题
FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;

22 可空值类型的三元运算

看如下代码:

 string str = "";
 double? a = double.TryParse(str, out double v) ? v : null;

意思就是,如果字符串str转换为数字,如果转换失败,则a的值为空,否则a的值为str对应的数字的值
但是上面的写法会报错:
c# 零碎笔记_第6张图片
那怎么办,难道只能用最笨的if else吗:

string str = "";
double? a = null;
if (double.TryParse(str, out double v))
{
    a = v;
}

作为一个有追求的程序员,不止满足于解决问题,于是可以写成下面这样:

string str = "";
double? a = double.TryParse(str, out double v) ? v : new double?();

最后运行,达到预期:
c# 零碎笔记_第7张图片

23 NLog记录异常,实现堆栈信息记录

1.首先,要保证有PDB文件
2.NLog的配置如下:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      throwExceptions="true"
      internalLogFile="c:\nlog1.txt"
      internalLogLevel="Debug"
      autoReload="true"
        >
  <!-- 设定日志扩展(即从*.dll加载NLog扩展) -->
  <extensions>
    <!--添加methodname所在的程序集-->
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <!-- 设定日志的目标输出 -->
  <targets>
    <!-- 定义输出模板:
		type="File":这个记录方式为文件类型
		fileName="${logDirectory}/All.log":表示输出到文件All.log中
		layout="...":输出文件中错误的显示格式
		${logDirectory}:为上述定义的路径
		${longdate}:输出长日期 yyyy-MM-dd HH:mm:ss.ffff(:2013-01-31 14:49:21.2120)
		${level}:错误等级(由低到高为Trace,Debug,Info,Warn,Error,Fatal)
		${newline}:输出 新的一行
		${stacktrace}:输出 堆栈信息
		${callsite:className=True:fileName=True:includeSourcePath=True:methodName=True}:输出 命名空间.类名.方法名(文件路径:行号)
		${message}:输出错误信息-->

  </targets>
  <targets async="true">
    <default-wrapper xsi:type="BufferingWrapper" bufferSize="1"/>

    <!--exception 由程序去拼接信息,放入到message中-->
    <target name="txt" type="File" fileName="./Log/${shortdate}/${processname}/${level}.log" 
      layout="${longdate} ${logger} THREAD[${threadid}] ${message} ${exception:format=tostring}"
      archiveAboveSize="1024000"
      maxArchiveFiles="10" />
  </targets>
  <!-- 设定日志的路由规则 -->
  <rules>
    <!--客户端的log-->
    <logger name="*" minlevel="trace"  writeTo="txt"/>

  </rules>

</nlog>

${exception:format=tostring}这一句就是输出异常的详细信息。输出的结果如下:
c# 零碎笔记_第8张图片

24 wpf创建具有最小高度的scrollbar

https://docs.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/how-to-customize-the-thumb-size-on-a-scrollbar?view=netframeworkdesktop-4.8

25 wpf GridSplitter在执行动画之后无法实现拖动

问题呈现

想实现的效果如下,展开时:
c# 零碎笔记_第9张图片
c# 零碎笔记_第10张图片
蓝色为GridSplitter,可以拖动。
折叠时:
c# 零碎笔记_第11张图片
发现,折叠后再展开(均带有GridLengthAnimation动画),GridSplitter不能拖动了。相关代码如下:
折叠动画:

orignalLeftWidth = this.LeftWidth;
GridLengthAnimation ani = new GridLengthAnimation();
ani.From = orignalLeftWidth;
ani.To = new GridLength(this.HideWidth);
ani.Duration = duration;
ani.Completed += (s, e) =>
{
    this.gridShow.Visibility = Visibility.Collapsed;
    this.gridHide.Visibility = Visibility.Visible;
};

this.BeginAnimation(CommonLayoutWindow.LeftWidthProperty, ani);

展开动画:

this.gridShow.Visibility = Visibility.Visible;
this.gridHide.Visibility = Visibility.Collapsed;
GridLengthAnimation ani = new GridLengthAnimation();
ani.From = new GridLength(this.HideWidth);
ani.To = orignalLeftWidth;
ani.Duration = duration;

this.BeginAnimation(CommonLayoutWindow.LeftWidthProperty, ani);

这样,运行程序之后,发现第一次可以拖动,折叠然后展开之后,就不能拖动了

问题解决

参考:https://docs.microsoft.com/zh-cn/dotnet/desktop/wpf/graphics-multimedia/how-to-set-a-property-after-animating-it-with-a-storyboard?redirectedfrom=MSDN&view=netframeworkdesktop-4.8#code-snippet-4
c# 零碎笔记_第12张图片
此文章提到的问题与当前遇到的问题一致,就是动画完成后,更改动画绑定的依赖属性无效,因为动画还在影响此依赖属性。
方法有三种,请参看具体的文章。本文使用第一种解决方案:将动画的 FillBehavior 属性设置为 Stop
更改后的代码:
折叠动画:

orignalLeftWidth = this.LeftWidth;
GridLengthAnimation ani = new GridLengthAnimation();
ani.From = orignalLeftWidth;
ani.To = new GridLength(this.HideWidth);
ani.Duration = duration;
ani.FillBehavior = System.Windows.Media.Animation.FillBehavior.Stop;
ani.Completed += (s, e) =>
{
    this.gridShow.Visibility = Visibility.Collapsed;
    this.gridHide.Visibility = Visibility.Visible;
    this.LeftWidth = new GridLength(this.HideWidth);
};

this.BeginAnimation(CommonLayoutWindow.LeftWidthProperty, ani);

展开动画:

this.gridShow.Visibility = Visibility.Visible;
this.gridHide.Visibility = Visibility.Collapsed;
GridLengthAnimation ani = new GridLengthAnimation();
ani.From = new GridLength(this.HideWidth);
ani.To = orignalLeftWidth;
ani.Duration = duration;
ani.FillBehavior = System.Windows.Media.Animation.FillBehavior.Stop;
ani.Completed += (s, e) =>
{
    this.LeftWidth = orignalLeftWidth;
};
this.BeginAnimation(CommonLayoutWindow.LeftWidthProperty, ani);

26 Newtonsoft.Json Camecal序列化

var settings = new Newtonsoft.Json.JsonSerializerSettings()
{
     ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
     NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
};
settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter(new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy(), false));
var json = Newtonsoft.Json.JsonConvert.SerializeObject(model, settings);

27 打印泛型类型

写一个万能的泛型方法,能够打印出类型的名称:

 static void Print<T>()
 {
      Console.WriteLine(nameof(T));
 }

测试:

internal class Program
{
     static void Main(string[] args)
     {            
         Print<Person>();
         Print<int>();
         Console.Read();
     }
     static void Print<T>()
     {
         Console.WriteLine(nameof(T));
     }
}
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

运行结果:
c# 零碎笔记_第13张图片

WTF,竟然不是想要的结果,原因是nameof在编译器就已经确定了名称,而不是在运行时
修改为如下代码:

static void Print<T>()
 {
      Console.WriteLine(typeof(T).Name);
 }

输出结果:
c# 零碎笔记_第14张图片
符合预期。

你可能感兴趣的:(C#,c#)