Laservall_solidworks_inject/Windows/ItemsPropWindow.xaml.cs

709 lines
25 KiB
C#
Raw Normal View History

using Laservall.Solidworks.Extension;
using Laservall.Solidworks.Model;
using Laservall.Solidworks.Windows.ViewModel;
using SolidWorks.Interop.sldworks;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Laservall.Solidworks.Windows
{
public class LevelToMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int level)
{
return new Thickness(level * 20, 0, 0, 0);
}
return new Thickness(0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ExpandIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool expanded)
{
return expanded ? "▼" : "▶";
}
return "▶";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public partial class ItemsPropWindow : System.Windows.Window
{
private List<BomItemModel> allBomItems = new List<BomItemModel>();
private HashSet<string> dirtyDocPaths = new HashSet<string>();
private BomSettings currentSettings;
private static readonly HashSet<string> StaticColumnProps = new HashSet<string>
{
"零件名称", "材质", "属性", "材料"
};
private static readonly Dictionary<string, string> PropNameToModelField = new Dictionary<string, string>
{
{ "PartName", "零件名称" },
{ "MaterialProp", "材料" },
{ "Material", "材质" },
{ "Classification", "属性" }
};
private static readonly Dictionary<string, string> BuiltInColumnNameMap = new Dictionary<string, string>
{
{ "层级", "LevelColumn" },
{ "图号", "DrawingNoColumn" },
{ "零件名称", "ConfigNameColumn" },
{ "属性", "ClassificationColumn" },
{ "材料", "MaterialPropColumn" },
{ "材质", "MaterialColumn" },
{ "数量", "QuantityColumn" },
{ "装配体", "IsAssemblyColumn" },
{ "外购件", "IsOutSourcingColumn" }
};
public ItemsPropWindow()
{
InitializeComponent();
if (DataContext == null)
{
DataContext = new ItemsViewModel();
}
currentSettings = BomSettings.Load();
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
if (!(DataContext is ItemsViewModel viewModel))
{
return;
}
viewModel.IsLoading = true;
viewModel.LoadingProgress = 0;
viewModel.LoadingProgressMaximum = 1;
viewModel.LoadingText = "正在读取数据...";
try
{
await System.Threading.Tasks.Task.Yield();
ModelDoc2 model = SWUtils.SwApp.ActiveDoc as ModelDoc2;
if (model == null)
{
viewModel.BomItems = new ObservableCollection<BomItemModel>();
return;
}
Configuration config = model.ConfigurationManager.ActiveConfiguration;
var allParts = new SwAssDocTreeModel()
{
drawingNo = Path.GetFileNameWithoutExtension(model.GetPathName()),
docPath = model.GetPathName(),
configName = config?.Name ?? "",
children = new List<SwAssDocTreeModel>(),
isAss = true,
quantity = 1,
props = SWUtils.GetConfig3(model.GetPathName())
};
Component2 rootComponent = config?.GetRootComponent3(true) as Component2;
int totalCount = GetTotalComponentCount(model);
viewModel.LoadingProgressMaximum = totalCount;
viewModel.LoadingText = "正在解析装配结构...";
SWUtils.SetAllPartResolved(model.GetPathName());
if (rootComponent != null)
{
var progress = new Progress<SwTreeLoadProgress>(p =>
{
viewModel.LoadingProgressMaximum = p.Total;
viewModel.LoadingProgress = p.Processed;
viewModel.LoadingText = string.IsNullOrWhiteSpace(p.CurrentName)
? $"正在读取数据 ({p.Processed}/{p.Total})"
: $"正在读取 {p.CurrentName} ({p.Processed}/{p.Total})";
});
var parts = await SWUtils.GetSwAssDocTreeChildrenAsync(rootComponent, true, true, totalCount, progress);
allParts.children.AddRange(parts);
}
viewModel.LoadingText = "正在生成BOM...";
allBomItems = SWUtils.FlattenBomTree(allParts);
var propKeys = SWUtils.CollectAllPropertyKeys(allBomItems);
var dynamicKeys = propKeys
.Where(k => !StaticColumnProps.Contains(k))
.OrderBy(k => k)
.ToList();
currentSettings.MergeDynamicKeys(dynamicKeys);
var userAddedKeys = currentSettings.Columns
.Where(c => c.IsUserAdded && !dynamicKeys.Contains(c.Name))
.Select(c => c.Name)
.ToList();
dynamicKeys.AddRange(userAddedKeys);
EnsurePropsKeys(dynamicKeys);
viewModel.AllPropertyKeys = dynamicKeys;
AddDynamicColumns(dynamicKeys);
ApplyColumnVisibility();
ApplyViewMode(viewModel);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(this, ex.Message, "读取数据失败", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
viewModel.IsLoading = false;
}
}
private void AddDynamicColumns(List<string> propKeys)
{
foreach (var key in propKeys)
{
var column = new DataGridTextColumn
{
Header = key,
Width = new DataGridLength(100),
Binding = new Binding($"Props[{key}]")
{
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus
}
};
BomDataGrid.Columns.Add(column);
}
}
private void EnsurePropsKeys(List<string> keys)
{
foreach (var item in allBomItems)
{
if (item.Props == null)
{
item.Props = new Dictionary<string, string>();
}
foreach (var key in keys)
{
if (!item.Props.ContainsKey(key))
{
item.Props[key] = "";
}
}
}
}
private void ApplyColumnVisibility()
{
if (currentSettings == null)
{
return;
}
foreach (var col in BomDataGrid.Columns)
{
string header = col.Header?.ToString();
if (string.IsNullOrEmpty(header))
{
continue;
}
var config = currentSettings.GetColumn(header);
if (config != null)
{
col.Visibility = config.IsVisible ? Visibility.Visible : Visibility.Collapsed;
}
}
}
private void ColumnConfig_Click(object sender, RoutedEventArgs e)
{
var configWindow = new ColumnConfigWindow(currentSettings);
configWindow.Owner = this;
if (configWindow.ShowDialog() == true && configWindow.ResultSettings != null)
{
currentSettings = configWindow.ResultSettings;
currentSettings.Save();
if (DataContext is ItemsViewModel viewModel)
{
var existingDynamicHeaders = new HashSet<string>();
var toRemove = new List<DataGridColumn>();
foreach (var col in BomDataGrid.Columns)
{
string header = col.Header?.ToString();
if (!string.IsNullOrEmpty(header) && !BuiltInColumnNameMap.ContainsKey(header))
{
existingDynamicHeaders.Add(header);
toRemove.Add(col);
}
}
foreach (var col in toRemove)
{
BomDataGrid.Columns.Remove(col);
}
var dynamicKeys = currentSettings.Columns
.Where(c => !c.IsFixed)
.Select(c => c.Name)
.ToList();
EnsurePropsKeys(dynamicKeys);
viewModel.AllPropertyKeys = dynamicKeys;
AddDynamicColumns(dynamicKeys);
}
ApplyColumnVisibility();
}
}
private void ToggleExpand_Click(object sender, RoutedEventArgs e)
{
if (!(sender is System.Windows.Controls.Primitives.ToggleButton toggle))
{
return;
}
if (!(toggle.DataContext is BomItemModel item))
{
return;
}
UpdateChildrenVisibility(item);
}
private void UpdateChildrenVisibility(BomItemModel parentItem)
{
bool shouldShow = parentItem.IsExpanded;
var descendants = GetDescendants(parentItem.NodeId);
foreach (var child in descendants)
{
if (!shouldShow)
{
child.IsVisible = false;
}
else
{
child.IsVisible = IsAncestorChainExpanded(child);
}
}
}
private bool IsAncestorChainExpanded(BomItemModel item)
{
int parentId = item.ParentNodeId;
while (parentId >= 0)
{
var parent = allBomItems.FirstOrDefault(b => b.NodeId == parentId);
if (parent == null)
{
break;
}
if (!parent.IsExpanded)
{
return false;
}
parentId = parent.ParentNodeId;
}
return true;
}
private List<BomItemModel> GetDescendants(int parentNodeId)
{
var result = new List<BomItemModel>();
var directChildren = allBomItems.Where(b => b.ParentNodeId == parentNodeId).ToList();
foreach (var child in directChildren)
{
result.Add(child);
result.AddRange(GetDescendants(child.NodeId));
}
return result;
}
private void ExpandAll_Click(object sender, RoutedEventArgs e)
{
foreach (var item in allBomItems)
{
item.IsExpanded = true;
item.IsVisible = true;
}
}
private void CollapseAll_Click(object sender, RoutedEventArgs e)
{
foreach (var item in allBomItems)
{
if (item.HasChildren)
{
item.IsExpanded = false;
}
item.IsVisible = item.Level == 0;
}
}
private void BomDataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
if (e.EditAction == DataGridEditAction.Cancel)
{
return;
}
if (!(e.Row.Item is BomItemModel item))
{
return;
}
if (!string.IsNullOrEmpty(item.DocPath))
{
dirtyDocPaths.Add(item.DocPath);
}
if (e.EditingElement is TextBox textBox)
{
string newValue = textBox.Text;
string header = e.Column.Header?.ToString() ?? "";
if (PropNameToModelField.ContainsValue(header))
{
if (item.Props != null)
{
item.Props[header] = newValue;
}
}
else if (item.Props != null && item.Props.ContainsKey(header))
{
item.Props[header] = newValue;
}
}
}
private void SaveChanges_Click(object sender, RoutedEventArgs e)
{
if (dirtyDocPaths.Count == 0)
{
System.Windows.MessageBox.Show(this, "没有需要保存的修改", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
try
{
int savedCount = 0;
foreach (var docPath in dirtyDocPaths)
{
var item = allBomItems.FirstOrDefault(b => b.DocPath == docPath);
if (item == null || item.Props == null)
{
continue;
}
ModelDoc2 doc = SWUtils.OpenDocSilently(docPath);
if (doc == null)
{
continue;
}
Configuration activeConfig = doc.ConfigurationManager?.ActiveConfiguration;
CustomPropertyManager configPropMgr = activeConfig?.CustomPropertyManager;
CustomPropertyManager filePropMgr = null;
try
{
filePropMgr = doc.Extension?.get_CustomPropertyManager("");
}
catch { }
foreach (var kvp in item.Props)
{
if (string.IsNullOrEmpty(kvp.Key)) continue;
bool writtenToConfig = false;
if (configPropMgr != null)
{
var configNames = configPropMgr.GetNames() as string[];
if (configNames != null && configNames.Contains(kvp.Key))
{
configPropMgr.Set2(kvp.Key, kvp.Value ?? "");
writtenToConfig = true;
}
}
if (!writtenToConfig && filePropMgr != null)
{
filePropMgr.Set2(kvp.Key, kvp.Value ?? "");
}
else if (!writtenToConfig && configPropMgr != null)
{
configPropMgr.Set2(kvp.Key, kvp.Value ?? "");
}
}
doc.Save3(0, 0, 0);
savedCount++;
}
dirtyDocPaths.Clear();
System.Windows.MessageBox.Show(this, $"已保存 {savedCount} 个文件的属性修改", "保存成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(this, ex.Message, "保存失败", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void SearchBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (!(DataContext is ItemsViewModel viewModel))
{
return;
}
string keyword = SearchBox.Text?.Trim() ?? "";
var sourceList = GetCurrentSourceList();
if (string.IsNullOrEmpty(keyword))
{
viewModel.BomItems = new ObservableCollection<BomItemModel>(sourceList);
return;
}
var filtered = sourceList.Where(item =>
(item.DrawingNo != null && item.DrawingNo.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) ||
(item.ConfigName != null && item.ConfigName.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) ||
(item.Material != null && item.Material.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) ||
(item.MaterialProp != null && item.MaterialProp.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) ||
(item.Classification != null && item.Classification.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) ||
(item.Props != null && item.Props.Values.Any(v => v != null && v.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0))
).ToList();
viewModel.BomItems = new ObservableCollection<BomItemModel>(filtered);
}
private void ExportExcel_Click(object sender, RoutedEventArgs e)
{
if (!(DataContext is ItemsViewModel viewModel) || viewModel.BomItems == null || viewModel.BomItems.Count == 0)
{
System.Windows.MessageBox.Show(this, "没有数据可导出", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var dialog = new Microsoft.Win32.SaveFileDialog
{
Filter = "CSV 文件|*.csv",
DefaultExt = ".csv",
FileName = "BOM_Export"
};
if (dialog.ShowDialog() != true)
{
return;
}
try
{
var sb = new StringBuilder();
bool isFlatView = viewModel.IsFlatView;
var builtInDefs = new List<(string Name, Func<BomItemModel, string> Getter, bool FlatOnly)>
{
("层级", item => item.LevelDisplay ?? "", false),
("图号", item => item.DrawingNo ?? "", false),
("零件名称", item => item.ConfigName ?? "", false),
("属性", item => item.Classification ?? "", false),
("材料", item => item.MaterialProp ?? "", false),
("材质", item => item.Material ?? "", false),
("数量", item => item.Quantity.ToString(), false),
("装配体", item => item.IsAssembly ? "是" : "否", false),
("外购件", item => item.IsOutSourcing ? "是" : "否", false)
};
var exportBuiltIns = builtInDefs
.Where(d => currentSettings.IsColumnExport(d.Name))
.Where(d => !isFlatView || (d.Name != "层级" && d.Name != "装配体"))
.ToList();
var exportDynamicKeys = (viewModel.AllPropertyKeys ?? new List<string>())
.Where(k => currentSettings.IsColumnExport(k))
.ToList();
var headers = exportBuiltIns.Select(d => d.Name).Concat(exportDynamicKeys).ToList();
sb.AppendLine(string.Join(",", headers.Select(EscapeCsvField)));
foreach (var item in viewModel.BomItems)
{
var fields = new List<string>();
foreach (var def in exportBuiltIns)
{
fields.Add(def.Getter(item));
}
foreach (var key in exportDynamicKeys)
{
fields.Add(item.GetProp(key));
}
sb.AppendLine(string.Join(",", fields.Select(EscapeCsvField)));
}
File.WriteAllText(dialog.FileName, sb.ToString(), Encoding.UTF8);
System.Windows.MessageBox.Show(this, $"已导出到:\n{dialog.FileName}", "导出成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(this, ex.Message, "导出失败", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static string EscapeCsvField(string field)
{
if (string.IsNullOrEmpty(field))
{
return "";
}
if (field.Contains(",") || field.Contains("\"") || field.Contains("\n"))
{
return "\"" + field.Replace("\"", "\"\"") + "\"";
}
return field;
}
private void FlatViewToggle_Changed(object sender, RoutedEventArgs e)
{
if (!(DataContext is ItemsViewModel viewModel))
{
return;
}
ApplyViewMode(viewModel);
}
private void ApplyViewMode(ItemsViewModel viewModel)
{
bool isFlatView = viewModel.IsFlatView;
LevelColumn.Visibility = isFlatView ? Visibility.Collapsed : Visibility.Visible;
ExpandAllBtn.Visibility = isFlatView ? Visibility.Collapsed : Visibility.Visible;
CollapseAllBtn.Visibility = isFlatView ? Visibility.Collapsed : Visibility.Visible;
if (isFlatView)
{
var flat = allBomItems
.Where(i => !i.IsAssembly)
.GroupBy(i => i.DrawingNo ?? "")
.Select(g =>
{
var first = g.First();
return new BomItemModel
{
Level = 0,
LevelDisplay = "",
DrawingNo = first.DrawingNo,
ConfigName = first.ConfigName,
PartName = first.PartName,
MaterialProp = first.MaterialProp,
Material = first.Material,
Classification = first.Classification,
Quantity = g.Sum(x => x.Quantity),
IsAssembly = false,
IsOutSourcing = first.IsOutSourcing,
DocPath = first.DocPath,
Props = first.Props,
HasChildren = false,
IsExpanded = false,
IsVisible = true
};
})
.ToList();
viewModel.BomItems = new ObservableCollection<BomItemModel>(flat);
}
else
{
viewModel.BomItems = new ObservableCollection<BomItemModel>(allBomItems);
}
}
private List<BomItemModel> GetCurrentSourceList()
{
if (!(DataContext is ItemsViewModel viewModel))
{
return allBomItems;
}
if (viewModel.IsFlatView)
{
return allBomItems
.Where(i => !i.IsAssembly)
.GroupBy(i => i.DrawingNo ?? "")
.Select(g =>
{
var first = g.First();
return new BomItemModel
{
Level = 0,
LevelDisplay = "",
DrawingNo = first.DrawingNo,
ConfigName = first.ConfigName,
PartName = first.PartName,
MaterialProp = first.MaterialProp,
Material = first.Material,
Classification = first.Classification,
Quantity = g.Sum(x => x.Quantity),
IsAssembly = false,
IsOutSourcing = first.IsOutSourcing,
DocPath = first.DocPath,
Props = first.Props,
HasChildren = false,
IsExpanded = false,
IsVisible = true
};
})
.ToList();
}
return allBomItems;
}
private int GetTotalComponentCount(ModelDoc2 model)
{
AssemblyDoc assembly = model as AssemblyDoc;
if (assembly == null)
{
return 1;
}
object[] components = assembly.GetComponents(true) as object[];
return Math.Max((components?.Length ?? 0) + 1, 1);
}
}
}