UpdateSourceTrigger=PropertyChanged vs LostFocus:你真的用对了吗?

第一章:UpdateSourceTrigger 的核心概念与应用场景

UpdateSourceTrigger 是 WPF 数据绑定系统中的关键属性,用于控制目标(UI 元素)的值何时更新源对象(数据模型)。它决定了绑定源的更新时机,直接影响用户体验和数据一致性。

基本作用机制

在默认情况下,TextBox.Text 绑定的 UpdateSourceTrigger 设置为 LostFocus,意味着用户编辑文本框后,只有当焦点离开该控件时,源属性才会被更新。若设置为 PropertyChanged,则每次文本变更都会立即同步到源对象。

常用触发模式

  • Default:使用依赖属性的默认更新行为
  • LostFocus:控件失去焦点时更新源
  • PropertyChanged:目标属性变化时立即更新源
  • Explicit:仅在调用 UpdateSource() 方法时手动更新

典型 XAML 示例

<!-- 实时更新源 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />

<!-- 焦点丢失时更新 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=LostFocus}" />

<!-- 手动控制更新 -->
<TextBox x:Name="txtInput" Text="{Binding Name, UpdateSourceTrigger=Explicit}" />

适用场景对比

场景推荐模式说明
搜索框实时过滤PropertyChanged输入即触发查询,响应更灵敏
表单数据录入LostFocus减少频繁验证,提升性能
复杂校验逻辑Explicit由按钮提交时统一更新并校验
graph TD A[用户输入] --> B{UpdateSourceTrigger} B -->|PropertyChanged| C[立即更新源] B -->|LostFocus| D[焦点离开后更新] B -->|Explicit| E[调用UpdateSource()才更新]

第二章:深入理解 UpdateSourceTrigger 的工作机制

2.1 PropertyChanged 触发模式的底层原理

在 WPF 和 MVVM 框架中,PropertyChanged 事件是实现数据绑定自动更新的核心机制。它依赖于 INotifyPropertyChanged 接口,当模型属性变更时主动通知 UI 层进行刷新。
事件触发流程
当属性 setter 被调用时,会手动或通过 AOP 方式触发 PropertyChanged 事件,携带属性名称作为参数:
public string Name
{
    get => _name;
    set
    {
        if (_name != value)
        {
            _name = value;
            OnPropertyChanged(nameof(Name)); // 通知变更
        }
    }
}
上述代码中,OnPropertyChanged 方法负责 Raise 事件,WPF 的绑定引擎监听该事件并更新对应 UI 元素。
性能优化策略
  • 避免在未改变值时触发事件,减少不必要的 UI 刷新
  • 使用表达式树或编译器生成技术(如 Fody)消除魔法字符串
  • 跨线程更新需调度至 UI 线程,防止异常

2.2 LostFocus 触发模式的执行时机分析

事件触发机制解析
LostFocus 事件在控件失去键盘焦点时触发,常用于数据验证或状态同步。其执行时机依赖于 UI 线程的消息循环处理顺序。
典型应用场景与代码示例
private void textBox1_LostFocus(object sender, EventArgs e)
{
    if (!IsValidInput(textBox1.Text))
    {
        errorProvider1.SetError(textBox1, "输入无效");
    }
    else
    {
        errorProvider1.SetError(textBox1, "");
    }
}
上述代码在文本框失去焦点时校验输入合法性。参数 sender 指向触发事件的控件,e 封装事件数据。该逻辑确保用户离开字段前完成即时反馈。
  • LostFocus 在 Enter 事件后发生
  • 控件切换、窗口失焦均可触发
  • 异步操作中需注意跨线程访问限制

2.3 默认行为在不同控件中的差异表现

在图形用户界面开发中,不同控件对默认行为的响应机制存在显著差异。例如,按钮控件通常在获得焦点时将“回车键”视为触发点击事件的信号,而文本框则将其作为换行或提交表单的指令。
常见控件的默认行为对比
  • Button:按下 Enter 键触发 click 事件
  • TextInput:Enter 键可能插入换行或提交表单
  • Checkbox:空格键用于切换选中状态
代码示例:阻止默认行为
document.getElementById('input').addEventListener('keydown', function(e) {
  if (e.key === 'Enter') {
    e.preventDefault(); // 阻止默认换行或提交
    submitForm();
  }
});
上述代码通过 preventDefault() 方法干预原生行为,实现自定义逻辑。这种机制在表单控制和快捷键设计中尤为关键。

2.4 BindingExpression 与源更新的同步过程

数据同步机制
在 WPF 中,BindingExpression 是绑定系统的核心组件,负责管理目标与源之间的数据流。当目标属性发生变化时,若 UpdateSourceTrigger 设置为 PropertyChanged,则立即触发源更新。
// 示例:手动触发源更新
BindingExpression bindingExpr = textBox.GetBindingExpression(TextBox.TextProperty);
bindingExpr?.UpdateSource();
上述代码通过获取文本框的绑定表达式,并调用 UpdateSource() 方法,强制将目标值写回数据源。该操作遵循绑定的 ValidationRulesConverter 流程。
更新生命周期
源更新过程包含多个阶段:目标值转换、数据验证、属性设置。若任一环节失败,BindingExpression 将进入错误状态并触发 ValidationErrorTemplate
  • 目标属性变更触发 TransferValue
  • 执行转换器(IValueConverter)逻辑
  • 调用源属性 setter
  • 更新结果反馈至绑定状态

2.5 多线程环境下触发机制的稳定性探讨

在高并发场景中,多线程环境下的事件触发机制面临竞态条件、资源争用和状态不一致等挑战。为确保稳定性,需采用线程安全的设计模式与同步机制。
锁机制与原子操作
使用互斥锁(Mutex)可防止多个线程同时访问共享资源。例如,在Go语言中:
var mu sync.Mutex
var counter int

func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++ // 线程安全的递增操作
}
该代码通过sync.Mutex确保对counter的修改是串行化的,避免数据竞争。
常见问题与解决方案对比
问题原因解决方案
竞态条件多线程读写共享变量加锁或使用原子操作
死锁循环等待锁资源统一锁顺序、设置超时

第三章:实际开发中的典型使用场景

3.1 实时验证场景下 PropertyChanged 的应用实践

在实时数据验证场景中,PropertyChanged 事件是实现响应式 UI 更新的核心机制。通过实现 INotifyPropertyChanged 接口,模型能够主动通知界面层属性值的变化,从而触发校验逻辑。
数据绑定与验证联动
当用户输入触发属性变更时,事件驱动校验器即时执行。例如:
public string Email
{
    get => _email;
    set
    {
        _email = value;
        OnPropertyChanged();
        ValidateEmail(); // 属性变更后立即验证
    }
}
上述代码中,OnPropertyChanged 通知 UI 更新,同时调用 ValidateEmail() 执行业务规则检查,实现反馈闭环。
常见验证策略对比
策略响应速度资源消耗
实时验证毫秒级中等
延迟验证500ms+

3.2 表单提交优化中 LostFocus 的合理运用

在表单交互设计中,LostFocus 事件常用于实时校验用户输入的合法性,避免提交时集中暴露多个错误。通过监听字段失焦事件,可实现数据即时验证与反馈。
触发时机与性能权衡
LostFocus 在用户离开输入框时触发,相比 Input 事件更节省资源,适合高延迟校验(如远程去重检查)。

document.getElementById('username').addEventListener('blur', async function() {
    const value = this.value;
    if (value.length < 3) return;
    const response = await fetch(`/api/check-username?name=${value}`);
    if (!response.ok) showError(this, '校验失败');
});
上述代码在用户名输入框失焦后发起唯一性校验,避免频繁请求。参数 blur 事件对应 LostFocus,确保仅在用户完成输入后触发。
与提交流程协同
合理结合 LostFocus 与表单提交,可减少重复校验,提升用户体验。

3.3 组合控件中触发策略的协同设计

在复杂UI系统中,组合控件的触发策略需确保子组件行为一致且响应连贯。关键在于定义清晰的事件传播机制与状态同步规则。
事件冒泡与拦截
当多个嵌套控件绑定相似事件时,应明确冒泡顺序与拦截条件:
element.addEventListener('click', (e) => {
  e.stopPropagation(); // 阻止向上冒泡
  triggerAction();
}, false);
该机制避免重复触发,提升响应精确度。
协同策略配置表
策略类型适用场景执行优先级
串行触发表单验证
并行触发多选操作

第四章:性能与用户体验的权衡策略

4.1 高频更新导致的性能瓶颈识别

在高并发系统中,数据的高频更新常引发性能瓶颈,主要体现在数据库锁竞争、缓存失效和I/O负载上升。
典型瓶颈场景
  • 短时间内大量写请求导致行锁或间隙锁堆积
  • 缓存穿透与雪崩因频繁更新而加剧
  • 磁盘I/O在日志刷盘时成为系统瓶颈
代码示例:高频计数器更新
func IncrementCounter(id int) error {
    _, err := db.Exec("UPDATE counters SET value = value + 1 WHERE id = ?", id)
    return err
}
该函数在每秒数千次调用下会引发表级锁争用。value字段无索引时,查询成本进一步升高,执行计划恶化。
性能监控指标
指标阈值说明
QPS>5000写入频率过高预警
平均响应延迟>50ms可能已出现锁等待

4.2 减少不必要的属性变更通知技巧

在响应式系统中,频繁的属性变更通知会导致性能下降。通过优化变更触发机制,可显著减少冗余更新。
使用脏检查阈值控制通知频率
仅当属性变化超过预设阈值时才触发通知,避免微小变动引发连锁更新。
function setProperty(value) {
  if (Math.abs(this.value - value) < EPSILON) return;
  this.value = value;
  notifyChange();
}
上述代码中,EPSILON 定义了最小有效变化量,防止浮点运算误差导致的重复通知。
批量合并属性更新
将多个属性修改合并为一次通知,提升整体响应效率。
  • 使用事务机制暂存变更
  • 延迟通知至所有修改完成
  • 结合事件队列实现异步派发

4.3 延迟更新与节流机制的辅助实现

在高频率数据变更场景中,直接同步状态易导致性能瓶颈。延迟更新通过暂存变更、合并操作来降低系统负载。
节流函数的实现
function throttle(fn, delay) {
  let inThrottle = false;
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, delay);
    }
  };
}
该实现确保函数在指定延迟周期内最多执行一次,inThrottle 标志位防止重复调用,适用于窗口滚动或输入事件监听。
应用场景对比
机制触发时机适用场景
延迟更新累积一定时间后批量提交表单自动保存
节流固定间隔执行一次搜索建议请求

4.4 用户输入流畅性与数据一致性的平衡

在现代Web应用中,既要保障用户输入的实时响应,又要确保后端数据的一致性,二者之间的权衡至关重要。
防抖与节流机制
为提升输入流畅性,常采用防抖(Debounce)策略,避免频繁触发数据同步。例如:
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
// 每次输入后延迟300ms再提交
const saveInput = debounce(saveToServer, 300);
该函数确保用户停止输入300毫秒后再发起请求,减少无效通信,同时保持界面响应。
乐观更新与冲突处理
前端可采用乐观更新立即渲染结果,随后与服务端同步。若发生冲突,需通过版本号机制解决:
字段类型说明
valuestring用户输入值
versionnumber数据版本号,用于并发控制
服务端比对版本号,拒绝过期写入,从而保障数据一致性。

第五章:最佳实践总结与未来展望

构建高可用微服务架构的关键策略
在生产环境中保障服务稳定性,需结合熔断、限流与健康检查机制。以 Go 语言实现的限流器为例:

package main

import (
    "golang.org/x/time/rate"
    "net/http"
)

var limiter = rate.NewLimiter(10, 50) // 每秒10个令牌,突发50

func handler(w http.ResponseWriter, r *http.Request) {
    if !limiter.Allow() {
        http.Error(w, "速率超限", http.StatusTooManyRequests)
        return
    }
    w.Write([]byte("处理请求"))
}
DevOps 流水线优化建议
持续集成阶段应包含静态扫描、单元测试与镜像构建。推荐流程如下:
  • 代码提交触发 CI 流水线
  • 使用 SonarQube 进行代码质量分析
  • 并行执行单元测试与安全扫描(如 Trivy)
  • 通过 Kaniko 构建不可变镜像并推送至私有仓库
  • 自动部署至预发环境并运行冒烟测试
云原生技术演进趋势
技术方向当前应用未来三年预期
服务网格Istio 在金融系统中广泛采用向轻量化、无代理架构演进
Serverless用于事件驱动型任务处理支持长时运行服务,提升冷启动性能
可观测性体系设计要点
日志、指标与追踪应统一采集至中央平台。例如使用 OpenTelemetry 同时上报 traces 和 metrics 至 Prometheus 与 Jaeger,通过 Grafana 实现关联分析,快速定位跨服务延迟瓶颈。
为什么EQUIPMENT PART NAME的grid没有填充下面的页面 而是只填充了一半 <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid Grid.Row="0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Text="Position Name" Grid.Row="0" Grid.Column="0" Margin="3,3,7,3" Style="{StaticResource LabelTextBlockStyle}"></TextBlock> <controls:MAgCombo Name="cmbPositionName" IsEditable="True" Grid.Column="1" MaskUnexistedInput="True" QueryDataCommand="{Binding Path=QueryEquipmentPartNameListCommand}" IsOptional="True" TargetFilePath="{Binding Path=EquipmentPartNameTargetFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Path=EquipmentPartNameSource,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" TextEmptyCommand="{Binding Path=EquipmentPartNameTextEmptyCommand}"> <controls:MAgCombo.SelectedText> <Binding Path="EquipmentPartName" Mode="TwoWay" UpdateSourceTrigger="LostFocus"> <Binding.ValidationRules> <m:IUIValidator MethodName="EquipmentPartNameTryValidation"></m:IUIValidator> </Binding.ValidationRules> </Binding> </controls:MAgCombo.SelectedText> </controls:MAgCombo> <TextBlock Text="Equipment Name" Grid.Row="1" Grid.Column="2" Margin="3,3,7,3" Style="{StaticResource LabelTextBlockStyle}"></TextBlock> <controls:MAgCombo Name="cmbEquipmentPartName" IsEditable="True" Grid.Row="1" Grid.Column="3" MaskUnexistedInput="True" QueryDataCommand="{Binding Path=QueryEquipmentPartNameListCommand}" IsOptional="True" TargetFilePath="{Binding Path=EquipmentPartNameTargetFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Path=EquipmentPartNameSource,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" TextEmptyCommand="{Binding Path=EquipmentPartNameTextEmptyCommand}"> <controls:MAgCombo.SelectedText> <Binding Path="EquipmentPartName" Mode="TwoWay" UpdateSourceTrigger="LostFocus"> <Binding.ValidationRules> <m:IUIValidator MethodName="EquipmentPartNameTryValidation"></m:IUIValidator> </Binding.ValidationRules> </Binding> </controls:MAgCombo.SelectedText> </controls:MAgCombo> <Button Grid.Column="5" Content="QUERY" Width="120" Margin="5,3,3,3" Command="{Binding QueryCommand}"></Button> </Grid> <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="auto"></ColumnDefinition> </Grid.ColumnDefinitions> <GroupBox Header="[EQUIPMENT PART NAME]" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="7" Margin="3"> <controls:LocalHeaderDataGrid Margin="0,0,6,6" SelectedItem="{Binding Path=SelectedEquipmentPartListRow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectionChangedCommand="{Binding Path=SelectedEquipmentPartChangedCommand}" SelectedIndex="{Binding Path= SelectedEquipmentPartIndex,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" TargetFilePath="{Binding Path= EquipmentPartTargetFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Stretch" CanUserAddRows="False" ColumnWidth="*" SelectionMode="Single" > </controls:LocalHeaderDataGrid> <!--<controls:LocalHeaderDataGrid Name="dgrdEquipmentPartName" ColumnWidth="*" SelectionMode="Extended" IsReadOnly="True" AutoGenerateColumns="False" CursorIndex="{Binding Path=CursorIndex,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Path=EquipmentPartNameInfoList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectionChangedCommand="{Binding Path=SelectedEquipmentPartNameListRowChangedCommand}" TargetFilePath="{Binding Path=EquipmentPartNameTargetFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedIndexes="{Binding Path= SelectedEquipmentPartNameIndexes,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" MultiSelectedItems="{Binding Path=SelectedEquipmentPartNameItemsRow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Path=SelectedEquipmentPartNameListRow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> --> <!--<controls:LocalHeaderDataGrid.Columns > <DataGridTextColumn Header="PHOTO RESIST NAME" Binding="{Binding EquipmentPartName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <DataGridTextColumn Header="PHOTO RESIST NUMBER" Binding="{Binding EquipmentPartNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <DataGridTextColumn Header="WARNING QTY" Binding="{Binding WarningQty,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <DataGridTextColumn Header="VISCOSTY" Binding="{Binding Viscosty,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <DataGridTextColumn Header="PRODUCTION FLAG" Binding="{Binding ProductionFlag,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <DataGridTextColumn Header="DEFROST HOUR" Binding="{Binding DefrostHour,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <DataGridTextColumn Header="INSTALLED VALID DAY" Binding="{Binding InstalledValidDay,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> </controls:LocalHeaderDataGrid.Columns>--> <!-- </controls:LocalHeaderDataGrid>--> </GroupBox> </Grid> <!-- <Grid Grid.Row="2"> --> <!-- <Grid.ColumnDefinitions> --> <!-- <ColumnDefinition Width="Auto"></ColumnDefinition> --> <!-- <ColumnDefinition Width="*"></ColumnDefinition> --> <!-- <ColumnDefinition Width="Auto"></ColumnDefinition> --> <!-- <ColumnDefinition Width="*"></ColumnDefinition> --> <!-- <ColumnDefinition Width="Auto"></ColumnDefinition> --> <!-- <ColumnDefinition Width="*"></ColumnDefinition> --> <!-- <ColumnDefinition Width="auto"></ColumnDefinition> --> <!-- </Grid.ColumnDefinitions> --> <!-- <GroupBox Header="[STOCK]" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="7" Margin="3"> --> <!-- --> <!-- <controls:LocalHeaderDataGrid Name="dgrdEquipmentPartName" --> <!-- ColumnWidth="*" --> <!-- SelectionMode="Single" --> <!-- IsReadOnly="True" --> <!-- AutoGenerateColumns="False" --> <!-- CursorIndex="{Binding Path=CursorIndex,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" --> <!-- ItemsSource="{Binding Path=StockInfoList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" --> <!-- SelectionChangedCommand="{Binding Path=SelectedStockListRowChangedCommand}" --> <!-- TargetFilePath="{Binding Path=StockTargetFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" --> <!-- --> <!-- SelectedIndexes="{Binding Path= StockIndexes,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" --> <!-- MultiSelectedItems="{Binding Path=SelectedStockItemsRow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" --> <!-- SelectedItem="{Binding Path=SelectedStockListRow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> --> <!-- --> <!-- --> <!-- <controls:LocalHeaderDataGrid.Columns > --> <!-- <DataGridTextColumn Header="STATUS" Binding="{Binding Status,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> --> <!-- <DataGridTextColumn Header="COUNT" Binding="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> --> <!-- </controls:LocalHeaderDataGrid.Columns> --> <!-- --> <!-- </controls:LocalHeaderDataGrid> --> <!-- </GroupBox> --> <!-- </Grid> --> <Grid Grid.Row="3" Grid.ColumnSpan="7"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="TOTAL COUNT" Margin="3,3,7,3" Style ="{StaticResource LabelTextBlockStyle}"></TextBlock> <TextBlock Style="{StaticResource ValueTextBlockStyle}" Grid.Row="0" Grid.Column="1" Background="LightGray" Text="{Binding Path=TotalCount,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></TextBlock> </Grid> <Grid Grid.Row="4" Grid.ColumnSpan="7"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Button Grid.Row="0" Content="CREATE" Margin="5,3,3,3" Command="{Binding CreateCommand}" IsEnabled="{Binding Path=IsCreateEnabled, Mode=TwoWay}"></Button> <Button Grid.Row="0" Grid.Column="1" Content="DELETE" Margin="5,3,3,3" Command="{Binding DeleteCommand}" IsEnabled="{Binding Path=IsDeleteEnabled, Mode=TwoWay}"></Button> <Button Grid.Row="0" Grid.Column="2" Content="MODIFY" Margin="5,3,3,3" Command="{Binding ModifyCommand}" IsEnabled="{Binding Path=IsModifyEnabled, Mode=TwoWay}"></Button> </Grid> </Grid>
最新发布
10-23
<Border SnapsToDevicePixels="True" CornerRadius="1" Background="White" > <customUserControl:ColorBorder x:Name="Border1" SnapsToDevicePixels="True" CornerRadius="1" LeftBorderBrush="#6d6d6d" TopBorderBrush="#6d6d6d" RightBorderBrush="#e3e3e3" BottomBorderBrush="#e3e3e3" BorderThickness="2,2,1,1" Background="White" > <Grid Background="{Binding ElementName=uc,Path=Background}"> <TextBox x:Name="PwTxTBox" Visibility ="Collapsed" Text="{Binding ElementName=uc,Path=Pwd,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" FontSize="{Binding ElementName=uc,Path=FontSize}" Width="{Binding ElementName=uc,Path=TextWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Height="{Binding ElementName=uc,Path=TextHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" BorderThickness="0" VerticalContentAlignment="Center" Style="{StaticResource PwTextBoxStyle}" CaretBrush="{Binding ElementName=uc,Path=CaretBrush}" Background="Transparent" Foreground="{Binding ElementName=uc,Path=CaretBrush}" Margin="5,0" FontWeight="{Binding ElementName=uc,Path=FontWeight}"> </TextBox> <PasswordBox Width="{Binding ElementName=uc,Path=TextWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Height="{Binding ElementName=uc,Path=TextHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" x:Name="PwBox" local:PasswordBoxHelper.Attach="True" Visibility="Visible" CaretBrush="{Binding ElementName=uc,Path=CaretBrush}" Foreground="{Binding ElementName=uc,Path=CaretBrush}" local:PasswordBoxHelper.Password="{Binding ElementName=uc,Path=Pwd,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="{Binding ElementName=uc,Path=FontSize}" VerticalAlignment="Center" BorderThickness="0" Style="{StaticResource PasswordBoxStyle}" Background="Transparent" VerticalContentAlignment="Center" PasswordChar="{Binding ElementName=uc,Path=PasswordChar}" Margin="5,0" FontWeight="{Binding ElementName=uc,Path=FontWeight}" GotFocus="PwBox_OnGotFocus" LostFocus="PwBox_OnLostFocus"> </PasswordBox> </Grid> </customUserControl:ColorBorder> <Border.Style> <Style TargetType="Border"> <Style.Triggers> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Opacity" Value="0.56"/> </Trigger> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Opacity" Value="0.8"/> </Trigger> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter Property="Opacity" Value="0.8"/> </Trigger> </Style.Triggers> </Style> </Border.Style> </Border>转为avalonia
09-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值