Browse Source

Merge remote-tracking branch 'origin/kenric' into frank

frankvandenbos 1 month ago
parent
commit
e28c516da0

+ 35 - 32
InABox.Avalonia/Components/AvaloniaDataGrid/AvaloniaDataGrid.axaml

@@ -26,38 +26,41 @@
 							  Text="{Binding $parent[components:AvaloniaDataGrid].SearchText}"
 							  Background="Transparent"
 							  Margin="0,0,0,5"/>
-		<DataGrid Name="Grid" Grid.Row="1" Grid.ColumnSpan="2"
-				  ItemsSource="{Binding $parent[components:AvaloniaDataGrid].ItemsSource}"
-				  RowBackground="White"
-				  Foreground="Black"
-				  AutoGenerateColumns="False"
-				  SelectionMode="{Binding $parent[components:AvaloniaDataGrid].SelectionMode}"
-				  Tapped="DataGrid_Tapped"
-				  SelectionChanged="DataGrid_SelectionChanged"
-				  RowHeight="{Binding $parent[components:AvaloniaDataGrid].RowHeight}"
-				  GridLinesVisibility="Vertical"
-				  FontSize="{StaticResource PrsFontSizeSmall}"
-				  CornerRadius="{StaticResource PrsCornerRadius}"
-				  BorderThickness="{StaticResource PrsBorderThickness}"
-				  BorderBrush="{StaticResource PrsTileBorder}">
-			<DataGrid.Styles>
-				<Style Selector="DataGridRow:nth-child(even)">
-					<Setter Property="Background" Value="WhiteSmoke"/>
-				</Style>
-				<Style Selector="DataGridColumnHeader:nth-child(1)">
-					<Setter Property="CornerRadius" Value="4,0,0,0"/>
-				</Style>
-				<Style Selector="DataGridColumnHeader:nth-last-child(1)">
-					<Setter Property="CornerRadius" Value="0,4,0,0"/>
-				</Style>
-				<Style Selector="DataGridColumnHeader">
-					<Setter Property="FontSize" Value="{StaticResource PrsFontSizeExtraSmall}"/>
-				</Style>
-				<Style Selector="DataGridCell">
-					<Setter Property="FontSize" Value="{StaticResource PrsFontSizeExtraSmall}"/>
-				</Style>
-			</DataGrid.Styles>
-		</DataGrid>
+		<Border Grid.Row="1" Grid.ColumnSpan="2"
+				CornerRadius="{StaticResource PrsCornerRadius}"
+				BorderThickness="{StaticResource PrsBorderThickness}"
+				BorderBrush="{StaticResource PrsTileBorder}"
+				ClipToBounds="True">
+			<DataGrid Name="Grid" Grid.Row="1" Grid.ColumnSpan="2"
+					  ItemsSource="{Binding $parent[components:AvaloniaDataGrid].ItemsSource}"
+					  RowBackground="White"
+					  Foreground="Black"
+					  AutoGenerateColumns="False"
+					  SelectionMode="{Binding $parent[components:AvaloniaDataGrid].SelectionMode}"
+					  Tapped="DataGrid_Tapped"
+					  SelectionChanged="DataGrid_SelectionChanged"
+					  RowHeight="{Binding $parent[components:AvaloniaDataGrid].RowHeight}"
+					  GridLinesVisibility="Vertical"
+					  FontSize="{StaticResource PrsFontSizeSmall}"
+					  CornerRadius="{StaticResource PrsCornerRadius}"
+					  >
+				<DataGrid.Styles>
+					<Style Selector="DataGridRow:nth-child(even)">
+						<Setter Property="Background" Value="WhiteSmoke"/>
+					</Style>
+					<Style Selector="DataGridColumnHeader">
+						<Setter Property="FontSize" Value="{StaticResource PrsFontSizeExtraSmall}"/>
+					</Style>
+					<Style Selector="DataGridCell">
+						<Setter Property="FontSize" Value="{StaticResource PrsFontSizeExtraSmall}"/>
+					</Style>
+
+					<Style Selector="DataGrid /template/ Border#DataGridBorder">
+						<Setter Property="ClipToBounds" Value="True"/>
+					</Style>
+				</DataGrid.Styles>
+			</DataGrid>
+		</Border>
 		
         <Border Name="_recordCountBox" Classes="Standard"
 				Grid.Row="2"

+ 0 - 21
InABox.Avalonia/Components/DateSelector/DateSelectorButton.axaml

@@ -1,21 +0,0 @@
-<UserControl xmlns="https://github.com/avaloniaui"
-			 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-			 xmlns:components="using:InABox.Avalonia.Components"
-			 x:Class="InABox.Avalonia.Components.DateSelectorButton"
-			 x:DataType="components:DateSelectorButton">
-	<UserControl.Resources>
-		<components:DateSelectorDateTimeFormatter x:Key="dateFormatter"/>
-	</UserControl.Resources>
-	<Button Classes="Standard"
-			Command="{Binding $parent[components:DateSelectorButton].ClickCommand}"
-			Padding="0"
-			Background="Transparent"
-			BorderThickness="0">
-		<Button.Content>
-			<MultiBinding Converter="{StaticResource dateFormatter}">
-				<Binding Path="$parent[components:DateSelectorButton].Date"/>
-				<Binding Path="$parent[components:DateSelectorButton]"/>
-			</MultiBinding>
-		</Button.Content>
-	</Button>
-</UserControl>

+ 1 - 6
InABox.Avalonia/Components/DateSelector/DateSelectorButton.axaml.cs → InABox.Avalonia/Components/DateSelector/DateSelectorButton.cs

@@ -19,7 +19,7 @@ public class DateSelectorDateChangedEventArgs(DateTime? oldDate, DateTime? newDa
     public DateTime? NewDate { get; set; } = newDate;
 }
 
-public partial class DateSelectorButton : UserControl
+public partial class DateSelectorButton : TemplatedControl
 {
     public static readonly StyledProperty<string> PromptProperty =
         AvaloniaProperty.Register<DateSelectorButton, string>(nameof(Prompt));
@@ -68,11 +68,6 @@ public partial class DateSelectorButton : UserControl
             args.NewValue is DateTime newDate ? newDate : null));
     }
 
-    public DateSelectorButton()
-    {
-        InitializeComponent();
-    }
-
     [RelayCommand]
     private async Task Click()
     {

+ 4 - 1
InABox.Avalonia/Components/DoubleBox/DoubleBox.cs

@@ -1,6 +1,7 @@
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Interactivity;
+using InABox.Core;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -23,7 +24,9 @@ public class DoubleBox : NumericUpDown
 
     private void TunnelTextEvent(object? sender, TextInputEventArgs e)
     {
-        e.Text = new string(e.Text?.Where(c => Char.IsDigit(c) || c == '.').ToArray());
+        e.Text = new string(e.Text?.WithIndex()
+            .Where(x => (x.Key == 0 && x.Value == '-') || Char.IsDigit(x.Value) || x.Value == '.')
+            .Select(x => x.Value).ToArray());
         if(e.Text == "")
         {
             e.Handled = true;

+ 4 - 1
InABox.Avalonia/Components/IntegerBox/IntegerBox.cs

@@ -1,6 +1,7 @@
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Interactivity;
+using InABox.Core;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -24,7 +25,9 @@ public class IntegerBox : NumericUpDown
 
     private void TunnelTextEvent(object? sender, TextInputEventArgs e)
     {
-        e.Text = new string(e.Text?.Where(c => Char.IsDigit(c)).ToArray());
+        e.Text = new string(e.Text?.WithIndex()
+            .Where(x => (x.Key == 0 && x.Value == '-') || Char.IsDigit(x.Value))
+            .Select(x => x.Value).ToArray());
         if(e.Text == "")
         {
             e.Handled = true;

+ 8 - 1
InABox.Avalonia/Components/MenuPanel/AvaloniaMenuItem.cs

@@ -57,7 +57,14 @@ public partial class AvaloniaMenuItem : ObservableObject
             }
             else if (action != null)
             {
-                _ = action();
+                var task = action();
+                task.ContinueWith(task =>
+                {
+                    if (task.Exception is not null)
+                    {
+                        MobileLogging.Log(task.Exception);
+                    }
+                });
             }
         });
 

+ 0 - 21
InABox.Avalonia/Components/TimeSelector/TimeSelectorButton.axaml

@@ -1,21 +0,0 @@
-<UserControl xmlns="https://github.com/avaloniaui"
-			 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-			 xmlns:components="using:InABox.Avalonia.Components"
-			 x:Class="InABox.Avalonia.Components.TimeSelectorButton"
-			 x:DataType="components:TimeSelectorButton">
-	<UserControl.Resources>
-		<components:TimeSelectorTimeSpanFormatter x:Key="timeFormatter"/>
-	</UserControl.Resources>
-	<Button Classes="Standard"
-			Command="{Binding $parent[components:TimeSelectorButton].ClickCommand}"
-			Padding="0"
-			Background="Transparent"
-			BorderThickness="0">
-		<Button.Content>
-			<MultiBinding Converter="{StaticResource timeFormatter}">
-				<Binding Path="$parent[components:TimeSelectorButton].Time"/>
-				<Binding Path="$parent[components:TimeSelectorButton]"/>
-			</MultiBinding>
-		</Button.Content>
-	</Button>
-</UserControl>

+ 1 - 6
InABox.Avalonia/Components/TimeSelector/TimeSelectorButton.axaml.cs → InABox.Avalonia/Components/TimeSelector/TimeSelectorButton.cs

@@ -19,7 +19,7 @@ public class TimeSelectorTimeChangedEventArgs(TimeSpan? oldTime, TimeSpan? newTi
     public TimeSpan? NewTime { get; set; } = newTime;
 }
 
-public partial class TimeSelectorButton : UserControl
+public partial class TimeSelectorButton : TemplatedControl
 {
     public static readonly StyledProperty<string> PromptProperty =
         AvaloniaProperty.Register<TimeSelectorButton, string>(nameof(Prompt));
@@ -68,11 +68,6 @@ public partial class TimeSelectorButton : UserControl
             args.NewValue is TimeSpan newTime ? newTime : null));
     }
 
-    public TimeSelectorButton()
-    {
-        InitializeComponent();
-    }
-
     [RelayCommand]
     private async Task Click()
     {

+ 4 - 6
InABox.Avalonia/InABox.Avalonia.csproj

@@ -12,6 +12,7 @@
 
     <ItemGroup>
       <None Remove="Images\cross.svg" />
+      <None Remove="Images\refresh.svg" />
       <None Remove="Images\search.svg" />
       <None Remove="Images\tick.svg" />
     </ItemGroup>
@@ -50,6 +51,9 @@
       <AvaloniaResource Include="Images\cross.svg">
         <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       </AvaloniaResource>
+      <AvaloniaResource Include="Images\refresh.svg">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </AvaloniaResource>
       <AvaloniaResource Include="Images\search.svg">
         <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       </AvaloniaResource>
@@ -59,15 +63,9 @@
     </ItemGroup>
 
     <ItemGroup>
-      <Compile Update="Components\TimeSelector\TimeSelectorButton.axaml.cs">
-        <DependentUpon>TimeSelectorButton.axaml</DependentUpon>
-      </Compile>
       <Compile Update="Components\TimeSelector\TimeSelectorView.axaml.cs">
         <DependentUpon>TimeSelectorView.axaml</DependentUpon>
       </Compile>
-      <Compile Update="Components\DateSelector\DateSelectorButton.axaml.cs">
-        <DependentUpon>DateSelectorButton.axaml</DependentUpon>
-      </Compile>
       <Compile Update="Components\ModuleGrid\PrsModuleGrid.axaml.cs">
         <DependentUpon>PrsModuleGrid.axaml</DependentUpon>
         <SubType>Code</SubType>

+ 15 - 0
InABox.Avalonia/Logging/MobileLogging.cs

@@ -19,6 +19,10 @@ namespace InABox.Avalonia
         private static Logger _logger = null;
 
         private static string _logfilenameincludingpath;
+
+        public delegate void ExceptionHandler(Exception ex, string? tag);
+
+        public static event ExceptionHandler? LogException;
         
         private static Logger CheckLogger()
         {
@@ -57,8 +61,19 @@ namespace InABox.Avalonia
             CheckLogger()
                 .Information("{Log}", message);
         }
+        
+        public static void LogError(string message)
+        {
+            CheckLogger()
+                .Error("{Log}", message);
+        }
 
         public static void Log(Exception exception, String tag = "")
+        {
+            LogException?.Invoke(exception, tag);
+        }
+
+        public static void LogExceptionMessage(Exception exception, string tag = "")
         {
             CheckLogger();
             if (String.IsNullOrWhiteSpace(tag))

+ 27 - 0
InABox.Avalonia/Theme/Classes/DateSelectorButton.axaml

@@ -2,6 +2,33 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 		xmlns:components="using:InABox.Avalonia.Components">
 	<Style Selector="components|DateSelectorButton">
+		<Style.Resources>
+			<components:DateSelectorDateTimeFormatter x:Key="dateFormatter"/>
+		</Style.Resources>
+		<Setter Property="Template">
+			<ControlTemplate>
+				<Button Classes="Standard"
+						Command="{Binding $parent[components:DateSelectorButton].ClickCommand}">
+					<Button.Styles>
+						<Style Selector="Button.Standard">
+							<Setter Property="CornerRadius" Value="{TemplateBinding CornerRadius}"/>
+							<Setter Property="Padding" Value="{TemplateBinding Padding}"/>
+							<Setter Property="Margin" Value="{TemplateBinding Margin}"/>
+							<Setter Property="Background" Value="{TemplateBinding Background}"/>
+							<Setter Property="Foreground" Value="{TemplateBinding Foreground}"/>
+							<Setter Property="BorderBrush" Value="{TemplateBinding BorderBrush}"/>
+							<Setter Property="BorderThickness" Value="{TemplateBinding BorderThickness}"/>
+						</Style>
+					</Button.Styles>
+					<Button.Content>
+						<MultiBinding Converter="{StaticResource dateFormatter}">
+							<Binding Path="$parent[components:DateSelectorButton].Date"/>
+							<Binding Path="$parent[components:DateSelectorButton]"/>
+						</MultiBinding>
+					</Button.Content>
+				</Button>
+			</ControlTemplate>
+		</Setter>
 		<Setter Property="BorderBrush" Value="{DynamicResource PrsButtonBorder}" />
 		<Setter Property="BorderThickness">
 			<Setter.Value>

+ 27 - 0
InABox.Avalonia/Theme/Classes/TimeSelectorButton.axaml

@@ -3,6 +3,33 @@
 		xmlns:components="using:InABox.Avalonia.Components"
 		xmlns:timeSelector="using:InABox.Avalonia.Components.TimeSelector">
 	<Style Selector="components|TimeSelectorButton">
+		<Style.Resources>
+			<components:TimeSelectorTimeSpanFormatter x:Key="timeFormatter"/>
+		</Style.Resources>
+		<Setter Property="Template">
+			<ControlTemplate>
+				<Button Classes="Standard"
+						Command="{Binding $parent[components:TimeSelectorButton].ClickCommand}">
+					<Button.Styles>
+						<Style Selector="Button.Standard">
+							<Setter Property="CornerRadius" Value="{TemplateBinding CornerRadius}"/>
+							<Setter Property="Padding" Value="{TemplateBinding Padding}"/>
+							<Setter Property="Margin" Value="{TemplateBinding Margin}"/>
+							<Setter Property="Background" Value="{TemplateBinding Background}"/>
+							<Setter Property="Foreground" Value="{TemplateBinding Foreground}"/>
+							<Setter Property="BorderBrush" Value="{TemplateBinding BorderBrush}"/>
+							<Setter Property="BorderThickness" Value="{TemplateBinding BorderThickness}"/>
+						</Style>
+					</Button.Styles>
+					<Button.Content>
+						<MultiBinding Converter="{StaticResource timeFormatter}">
+							<Binding Path="$parent[components:TimeSelectorButton].Time"/>
+							<Binding Path="$parent[components:TimeSelectorButton]"/>
+						</MultiBinding>
+					</Button.Content>
+				</Button>
+			</ControlTemplate>
+		</Setter>
 		<Setter Property="BorderBrush" Value="{DynamicResource PrsButtonBorder}" />
 		<Setter Property="BorderThickness">
 			<Setter.Value>

+ 6 - 0
InABox.Core/Client/Client.cs

@@ -236,6 +236,12 @@ namespace InABox.Clients
             return new Client<TEntity>().Query(filter, columns, orderby, range);
         }
 
+        public static Task<CoreTable> QueryAsync<TEntity>(Filter<TEntity>? filter = null, Columns<TEntity>? columns = null, SortOrder<TEntity>? orderby = null, CoreRange? range = null)
+            where TEntity : Entity, IRemotable, new()
+        {
+            return Task.Run(() => new Client<TEntity>().Query(filter, columns, orderby, range));
+        }
+
         public static void Query<TEntity>(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? orderby, CoreRange? range, Action<CoreTable?, Exception?> callback)
             where TEntity : Entity, IRemotable, new()
         {

+ 0 - 69
inabox.wpf/DynamicGrid/DynamicGridStyle.cs

@@ -315,73 +315,4 @@ public class DynamicGridCellStyleParameters
     }
 }
 
-public class DynamicGridGridCellStyleConverter<T> : IValueConverter
-{
-
-    private readonly IBaseDynamicGrid _grid;
-    
-    private readonly Func<CoreRow, DynamicColumnBase, T> _converter;
-    
-    public DynamicGridGridCellStyleConverter(IBaseDynamicGrid grid, Func<CoreRow, DynamicColumnBase, T> converter)
-    {
-        _grid = grid ?? throw new ArgumentNullException(nameof(grid));
-        _converter = converter ?? throw new ArgumentNullException(nameof(converter));
-    }
-
-    private CoreRow? GetRow(object item)
-    {
-        try
-        {
-            if (item is DataRowView row)
-            {
-                var index = row.Row.Table.Rows.IndexOf(row.Row);
-                return _grid.GetVisibleRow(index);
-                
-                //return _grid.Data.Rows[index];
-
-                // var map = _rowMap.FirstOrDefault(x => x.Value.Index == row.Index);
-                // if (!Equals(map,default(KeyValuePair<DataRow,CoreRow>)))
-                // {
-                //     //var datarow = table.Rows[row.Index];
-                //     for (var i = 0; i < rowdata.Count; i++)
-                //         map.Key[i] = rowdata[i] ?? DBNull.Value;
-                // }
-                // else
-                // {
-                //
-                // }
-
-
-
-            }
-            return null;
-        }
-        catch
-        {
-            return null;
-        }
-    }
-    
-    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
-    {
-        var row = GetRow(value);
-        if (row == null)
-            return DependencyProperty.UnsetValue;
-
-        var param = parameter as DynamicGridCellStyleParameters;
-        if (param == null)
-            return DependencyProperty.UnsetValue;
-        
-        //var column = parameter as DynamicColumnBase;
-        //if (column is null)
-        //    return DependencyProperty.UnsetValue;
-        
-        return _converter.Invoke(row,param.Column) ?? param.DefaultValue;
-    }
-    
-    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
-    {
-        throw new NotSupportedException();
-    }
-}
 

+ 0 - 2
inabox.wpf/DynamicGrid/Grids/BaseDynamicGrid.cs

@@ -379,8 +379,6 @@ public abstract class BaseDynamicGrid : ContentControl, IDynamicGridUIComponentP
     {
     }
 
-    public CoreRow GetVisibleRow(int index) => UIComponent.GetVisibleRow(index);
-
     #region IDynamicGridUIComponentParent
 
     protected virtual IDynamicGridUIComponent CreateUIComponent()

+ 0 - 2
inabox.wpf/DynamicGrid/Grids/IDynamicGrid.cs

@@ -98,8 +98,6 @@ public interface IBaseDynamicGrid
     object? GetData(CoreRow row, DynamicColumnBase column);
 
     IDynamicGridColumnFilter? GetColumnFilter(DynamicColumnBase column);
-
-    CoreRow GetVisibleRow(int index);
 }
 
 public interface IDynamicGrid : IBaseDynamicGrid

+ 74 - 54
inabox.wpf/DynamicGrid/UIComponent/DynamicGridGridUIComponent.cs

@@ -58,11 +58,11 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
         {
             _parent = value;
 
-            CellBackgroundConverter = new DynamicGridGridCellStyleConverter<System.Windows.Media.Brush?>(Parent, GetCellBackground);
-            CellForegroundConverter = new DynamicGridGridCellStyleConverter<System.Windows.Media.Brush?>(Parent, GetCellForeground);
-            CellFontSizeConverter = new DynamicGridGridCellStyleConverter<double?>(Parent, GetCellFontSize);
-            CellFontStyleConverter = new DynamicGridGridCellStyleConverter<System.Windows.FontStyle?>(Parent, GetCellFontStyle);
-            CellFontWeightConverter = new DynamicGridGridCellStyleConverter<System.Windows.FontWeight?>(Parent, GetCellFontWeight);
+            CellBackgroundConverter = new CellStyleConverter<System.Windows.Media.Brush?>(this, GetCellBackground);
+            CellForegroundConverter = new CellStyleConverter<System.Windows.Media.Brush?>(this, GetCellForeground);
+            CellFontSizeConverter = new CellStyleConverter<double?>(this, GetCellFontSize);
+            CellFontStyleConverter = new CellStyleConverter<System.Windows.FontStyle?>(this, GetCellFontStyle);
+            CellFontWeightConverter = new CellStyleConverter<System.Windows.FontWeight?>(this, GetCellFontWeight);
 
             DataGrid.RowStyleSelector = Parent.RowStyleSelector;
             DataGrid.TableSummaryRowStyleSelector = new SummaryRowStyleSelector(GetSummaryRowStyle);
@@ -273,8 +273,6 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
 
         return result.ToArray();
     }
-
-    public CoreRow GetVisibleRow(int index) => _rowMap.Values.ToArray()[index];
     
     private void SetSelectedRows(CoreRow[] rows)
     {           
@@ -283,7 +281,6 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
         var table = DataGridItems;
         if(table is null) return;
 
-
         var dataRows = rows.Select(x => _rowMap.FirstOrDefault(y => x == y.Value).Key);
         if (!Parent.Options.MultiSelect)
         {
@@ -689,11 +686,56 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
 
     #region Styles
 
-    private DynamicGridGridCellStyleConverter<System.Windows.Media.Brush?> CellBackgroundConverter = null!;
-    private DynamicGridGridCellStyleConverter<System.Windows.Media.Brush?> CellForegroundConverter = null!;
-    private DynamicGridGridCellStyleConverter<double?> CellFontSizeConverter = null!;
-    private DynamicGridGridCellStyleConverter<System.Windows.FontStyle?> CellFontStyleConverter = null!;
-    private DynamicGridGridCellStyleConverter<System.Windows.FontWeight?> CellFontWeightConverter = null!;
+    private class CellStyleConverter<TValue>(DynamicGridGridUIComponent component, Func<CoreRow, DynamicColumnBase, TValue> converter) : IValueConverter
+    {
+        private readonly DynamicGridGridUIComponent _component = component ?? throw new ArgumentNullException(nameof(component));
+        
+        private readonly Func<CoreRow, DynamicColumnBase, TValue> _converter = converter ?? throw new ArgumentNullException(nameof(converter));
+
+        private CoreRow? GetRow(object item)
+        {
+            try
+            {
+                if (item is DataRowView row)
+                {
+                    return component.GetRow(row.Row);
+                }
+                return null;
+            }
+            catch
+            {
+                return null;
+            }
+        }
+        
+        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            var row = GetRow(value);
+            if (row == null)
+                return DependencyProperty.UnsetValue;
+
+            var param = parameter as DynamicGridCellStyleParameters;
+            if (param == null)
+                return DependencyProperty.UnsetValue;
+            
+            //var column = parameter as DynamicColumnBase;
+            //if (column is null)
+            //    return DependencyProperty.UnsetValue;
+            
+            return _converter.Invoke(row,param.Column) ?? param.DefaultValue;
+        }
+        
+        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            throw new NotSupportedException();
+        }
+    }
+
+    private CellStyleConverter<System.Windows.Media.Brush?> CellBackgroundConverter = null!;
+    private CellStyleConverter<System.Windows.Media.Brush?> CellForegroundConverter = null!;
+    private CellStyleConverter<double?> CellFontSizeConverter = null!;
+    private CellStyleConverter<System.Windows.FontStyle?> CellFontStyleConverter = null!;
+    private CellStyleConverter<System.Windows.FontWeight?> CellFontWeightConverter = null!;
 
     protected virtual Brush? GetCellSelectionForegroundBrush() => DynamicGridUtils.SelectionForeground;
     protected virtual Brush? GetCellSelectionBackgroundBrush() => DynamicGridUtils.SelectionBackground;
@@ -1502,22 +1544,6 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
     {
         AddRows(page, false);
     }
-
-    // public CoreRow DataRowIndexToCoreRow(int index)
-    // {
-    //     var map = _rowMap.FirstOrDefault(x => x.Value.Index == row.Index);
-    //     if (!Equals(map,default(KeyValuePair<DataRow,CoreRow>)))
-    //     {
-    //         //var datarow = table.Rows[row.Index];
-    //         for (var i = 0; i < rowdata.Count; i++)
-    //             map.Key[i] = rowdata[i] ?? DBNull.Value;
-    //     }
-    //     else
-    //     {
-    //         
-    //     }
-    //
-    // }
     
     public void InvalidateRow(CoreRow row)
     {
@@ -1533,17 +1559,13 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
         foreach (var ac in ActionColumns)
             rowdata.Add(ac.Data(row));
 
-
-        var map = _rowMap.FirstOrDefault(x => x.Value.Index == row.Index);
-        if (!Equals(map,default(KeyValuePair<DataRow,CoreRow>)))
-        {
-            //var datarow = table.Rows[row.Index];
-            for (var i = 0; i < rowdata.Count; i++)
-                map.Key[i] = rowdata[i] ?? DBNull.Value;
-        }
-        else
+        var dataRow = GetDataRow(row);
+        if(dataRow is not null)
         {
-            
+            for(var i = 0; i < rowdata.Count; ++i)
+            {
+                dataRow[i] = rowdata[i] ?? DBNull.Value;
+            }
         }
 
         _invalidating = false;
@@ -1605,7 +1627,7 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
 
     protected DirectEditingObject EnsureEditingObject(CoreRow row)
     {
-        _editingObject ??= new(GetEditingObject(row), row, DataGridItems?.Rows[row.Index]);
+        _editingObject ??= new(GetEditingObject(row), row, GetDataRow(row));
         return _editingObject;
     }
 
@@ -1616,7 +1638,14 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
 
     private DataRow? GetDataRow(CoreRow row)
     {
-        return DataGridItems?.Rows[row.Index];
+        foreach(var (key, value) in _rowMap)
+        {
+            if(value == row)
+            {
+                return key;
+            }
+        }
+        return null;
     }
 
     void IDynamicGridUIComponent.UpdateCell(CoreRow row, string column, object? value)
@@ -1701,12 +1730,8 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
         Parent.UpdateData(coreRow, column, updates);
     }
 
-    private void UpdateData(int rowIndex, int columnIndex)
+    private void UpdateData(DataRow row, int columnIndex)
     {
-        var table = DataGridItems;
-        if (table is null)
-            return;
-        
         if (GetColumn(columnIndex) is DynamicGridColumn gridcol)
         {
             var datacol = Parent.Data.Columns.FirstOrDefault(x => x.ColumnName.Equals(gridcol.ColumnName));
@@ -1714,7 +1739,7 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
             {
                 var datacolindex = Parent.Data.Columns.IndexOf(datacol);
 
-                var value = table.Rows[rowIndex][datacolindex];
+                var value = row[datacolindex];
                 if (value is DBNull)
                     value = CoreUtils.GetDefault(datacol.DataType);
 
@@ -1752,9 +1777,9 @@ public class DynamicGridGridUIComponent : IDynamicGridUIComponent, IDynamicGridG
 
     private void DataGrid_CurrentCellEndEdit(object? sender, CurrentCellEndEditEventArgs e)
     {
-        if (_editingObject is not null && bChanged)
+        if (_editingObject is not null && bChanged && _editingObject.DataRow is not null)
         {
-            UpdateData(_editingObject.Row.Index, e.RowColumnIndex.ColumnIndex);
+            UpdateData(_editingObject.DataRow, e.RowColumnIndex.ColumnIndex);
         }
         if (bChanged)
             Parent.DoChanged();
@@ -1953,11 +1978,6 @@ public class DynamicGridGridUIComponent<T> : DynamicGridGridUIComponent, IDynami
         return Parent.LoadItem(row);
     }
 
-    private DataRow? GetDataRow(CoreRow row)
-    {
-        return DataGridItems?.Rows[row.Index];
-    }
-
     protected override void DoEntityChanged(IDynamicColumnBase column, DynamicColumnEntityChangedEventArgs args)
     {
         base.DoEntityChanged(column, args);

+ 25 - 15
inabox.wpf/DynamicGrid/UIComponent/DynamicGridTreeUIComponent.cs

@@ -212,10 +212,13 @@ public class DynamicGridTreeUIComponent<T, TKey> : IDynamicGridUIComponent<T>, I
         SetParentKey = setParentKey;
     }
 
-    public DynamicGridTreeUIComponent(Expression<Func<T, TKey>> idColumn, Expression<Func<T, TKey>> parentIDColumn, TKey nullKey): this()
+    public DynamicGridTreeUIComponent(Expression<Func<T, TKey>> idColumn, Expression<Func<T, TKey>>? parentIDColumn, TKey nullKey): this()
     {
         IDColumn = new Column<T>(CoreUtils.GetFullPropertyName(idColumn, "."));
-        ParentColumn = new Column<T>(CoreUtils.GetFullPropertyName(parentIDColumn, "."));
+        if(parentIDColumn is not null)
+        {
+            ParentColumn = new Column<T>(CoreUtils.GetFullPropertyName(parentIDColumn, "."));
+        }
         NullKey = nullKey;
     }
 
@@ -1004,22 +1007,26 @@ public class DynamicGridTreeUIComponent<T, TKey> : IDynamicGridUIComponent<T>, I
         return MapRow(node?.Row);
     }
 
+    /// <summary>
+    /// Map a row of <see cref="_innerTable"/> to a Data row.
+    /// </summary>
     private CoreRow? MapRow(CoreRow? row)
     {
         if (row is null) return null;
 
         return _rowMap.GetValueOrDefault(row);
     }
-    
-    public CoreRow GetVisibleRow(int index) => _rowMap.Values.ToArray()[index];
 
     private CoreTreeNode<TKey>? GetNode(CoreRow row)
     {
-        if (_innerTable is null || row.Index < 0 || row.Index >= _innerTable.Rows.Count) return null;
-
-        var _innerRow = _innerTable.Rows[row.Index];
-        var node = Nodes.Find(_innerRow);
-        return node;
+        foreach(var (innerRow, dataRow) in _rowMap)
+        {
+            if(row == dataRow)
+            {
+                return Nodes.Find(innerRow);
+            }
+        }
+        return null;
     }
 
     public CoreRow[] GetVisibleRows()
@@ -1669,6 +1676,9 @@ public class DynamicGridTreeUIComponent<T, TKey> : IDynamicGridUIComponent<T>, I
 
     public CoreTreeNodes<TKey> Nodes { get; set; }
 
+    /// <summary>
+    /// Map from _innerTable rows to Data rows.
+    /// </summary>
     private Dictionary<CoreRow, CoreRow> _rowMap = new();
 
     private CoreTable? _innerTable;
@@ -1797,14 +1807,14 @@ public class DynamicGridTreeUIComponent<T, TKey> : IDynamicGridUIComponent<T>, I
 
     public void InvalidateRow(CoreRow row)
     {
-        if (_innerTable is null || row.Index < 0 || row.Index >= _innerTable.Rows.Count) return;
         _invalidating = true;
 
-        var _innerRow = _innerTable.Rows[row.Index];
-        ProcessRow(_innerRow, row);
-
-        var coreTreeNode = Nodes.Find(_innerRow);
-        coreTreeNode?.InvalidateData();
+        var node = GetNode(row);
+        if(node is not null)
+        {
+            ProcessRow(node.Row, row);
+            node.InvalidateData();
+        }
 
         CalculateSummaries();
 

+ 0 - 1
inabox.wpf/DynamicGrid/UIComponent/IDynamicGridUIComponent.cs

@@ -96,7 +96,6 @@ public interface IDynamicGridUIComponent
 
     void ScrollIntoView(CoreRow row);
     CoreRow[] GetVisibleRows();
-    CoreRow GetVisibleRow(int index);
 }
 
 public interface IDynamicGridUIComponent<T> : IDynamicGridUIComponent