WPF-də özəl FlexGrid paneli: CSS Flexbox məntiqinin desktop proqramlaşdırmada əvəzi
WPF
WPF (Windows Presentation Foundation) - desktop, yəni kompüter proqramlarını gəlişdirmək üçün istifadə olunan freymvorkdur. WPF müxtəlif layout panelləri təqdim etsə də, CSS flexbox məntiqli panel mövcud deyil. Bu məqalədə WPF layout imkanlarını artırmaq üçün CSS flexbox tipli özəl FlexGrid yaradacayıq.
Panellər
Grid - sütun və sətirlərdən ibarət cədvəl təyin edir.
UniformGrid - elementləri bərabər xana ölçüləri olan bir şəbəkədə yerləşdirmək üçün istifadə olunur.
WrapPanel - elementləri ardıcıl formada yerləşdirir, sərhəddən keçdikdə isə onları kənardan növbəti sətirə keçirir. Orientation xüsusiyyətinin dəyərindən asılı olaraq ardıcıllığı yuxarıdan aşağıya və ya sağdan sola sıralayır.
Davamı burada.
CSS Flexbox
Flexbox - veb proqramlaşdırmada geniş istifadə olunan, çevik və müxtəlif dizaynları tərtib etməyə imkan verən paneldir. Flexbox, öz balalarını (daxili elementləri) sətir və ya sütunlarda dinamik şəkildə təşkil edə, müxtəlif xüsusiyyətlərə əsasən onların ölçülərini və mövqelərini düzəldə bilər.
Məqsədlər
Yazacağımız FlexGrid aşağıdakı məqsədləri hədəfləyir:
- Bölgü: Mövcud sahəni dinamik olaraq xanalara bölmək imkanı.
- Paylanma: Müəyyən edilmiş parametrlərə əsasən xanaların arasında məkanın paylanması.
- Uyğunlaşma: Panel ölçüsündə dəyişikliklərə uyğunlaşma.
- İstifadə: Proqramçıların istifadəsi üçün intuitiv interfeys.
Struktur
Sinifin təyin olunması
public class FlexGrid : UniformGrid
{
private FrameworkElement Owner;
public double ItemMinHeight
{
get => (double)GetValue(ItemMinHeightProperty);
set => SetValue(ItemMinHeightProperty, value);
}
public double ItemMinWidth
{
get => (double)GetValue(ItemMinWidthProperty);
set => SetValue(ItemMinWidthProperty, value);
}
public static readonly DependencyProperty ItemMinHeightProperty =
DependencyProperty.Register("ItemMinHeight", typeof(double), typeof(FlexGrid), new PropertyMetadata(0d));
public static readonly DependencyProperty ItemMinWidthProperty =
DependencyProperty.Register("ItemMinWidth", typeof(double), typeof(FlexGrid), new PropertyMetadata(0d));
}
Hər xananın minimum hündürlüyü və enini təyin etmək üçün ItemMinHeight və ItemMinWidth xüsusiyyətlərini əlavə etməliyik. Bu xüsusiyyətlərin sayəsində biz lazımi və mövcud olan sahəni bərabər şəkildə bölə biləcəyik. Owner xüsusiyyəti isə cari panelin ana elementini idarə etmək və dəyişiklikləri özündə əks etdirmək üçün istifadə olunacaq.
Paylanma alqoritmi
public class FlexGrid : UniformGrid
{
...
private void UpdateGrid(double actualHeight, double actualWidth) {
try
{
int rows = 0, columns = 0;
try
{
rows = (int)Math.Floor(actualHeight / ItemMinHeight);
columns = (int)Math.Floor(actualWidth / ItemMinWidth);
}
catch
{
rows = 1;
columns = 1;
}
rows = rows == 0 ? 1 : rows;
columns = columns == 0 ? 1 : columns;
var fitItemsCount = rows * columns;
if (fitItemsCount < Children.Count)
{
rows += (int)Math.Ceiling((Children.Count - fitItemsCount) / (double)columns);
}
Rows = rows;
Columns = columns;
}
catch { }
}
}
Əsas məntiq, mövcud boş sahəyə uyğun olaraq yerləşəcək elementlərin mümkün sayını hesablamaqdır. Eyni zamanda, ”overflow” baş verdikdə əlavə sətirlərin əlavə olunmasını nəzərə almalıyıq.
Yeniləmə prosesi
Yeniləmə prosesi əsaslı şəkildə iki halda baş verə bilər, xarici ölçülərin və ya daxili elementlərin sayı dəyişiləndə. Hər iki halı nəzərə alıb tətbiq etmək üçün aşağıdakı metodları əlavə edirik:
- Konstruktor - panel ölçülərin dəyişməsindən xəbərdar olmaq üçün.
- SetOwner - yuxarı səviyyəli (ana) element(lər)i tapmaq üçün.
- FlexGrid_CollectionChanged - panel elementlərin əlavə olunması/silinməsi hallarını idarə etmək üçün.
- FlexGrid_SizeChanged - panel ölçülərin yenilənmə halını idarə etmək üçün.
public class FlexGrid : UniformGrid
{
...
static FlexGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlexGrid), new FrameworkPropertyMetadata(typeof(FlexGrid)));
}
public FlexGrid()
{
SizeChanged += FlexGrid_SizeChanged;
}
private DependencyObject GetComplexParent(DependencyObject childObject)
{
return childObject.GetType().GetProperty(nameof(Parent))?.GetValue(childObject, null) as DependencyObject ??
childObject.GetType().GetProperty(nameof(VisualParent))?.GetValue(childObject, null) as DependencyObject ??
childObject.GetType().GetProperty(nameof(TemplatedParent))?.GetValue(childObject, null) as DependencyObject;
}
private void SetOwner()
{
if (Owner is null)
{
DependencyObject nextVisualParent = VisualParent;
while (true)
{
if (nextVisualParent is null)
{
nextVisualParent = VisualParent;
break;
}
else if (nextVisualParent is ItemsControl itemsControlParent)
{
if (itemsControlParent.Items != null)
{
(itemsControlParent.Items as INotifyCollectionChanged).CollectionChanged += FlexGrid_CollectionChanged;
}
nextVisualParent = GetComplexParent(nextVisualParent);
continue;
}
else if (!(nextVisualParent is ScrollViewer))
{
nextVisualParent = GetComplexParent(nextVisualParent);
continue;
}
else
{
break;
}
}
Owner = nextVisualParent as FrameworkElement;
}
}
private void FlexGrid_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (Owner is null)
{
UpdateGrid(ItemMinHeight, ItemMinWidth);
}
else
{
UpdateGrid(Owner.ActualHeight, Owner.ActualWidth);
}
}
private void FlexGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
SetOwner();
UpdateGrid(Owner.ActualHeight, Owner.ActualWidth);
e.Handled = true;
}
}
İstifadə nümunəsi
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ItemsContainer.ItemsSource = new ObservableCollection<string>
{
"MidnightBlue",
"CadetBlue",
"DodgerBlue",
"Blue",
"DarkBlue",
};
}
private void ContentControl_MouseDown(object sender, MouseButtonEventArgs e)
{
var collection = ItemsContainer.ItemsSource as ObservableCollection<string>;
collection.Add((sender as ContentControl).Content.ToString());
ItemsContainer.ItemsSource = collection;
}
}
MainWindow sinifində, ilkin siyahını təyin edirik və elementə kliklədikdə ona oxşar yeni elementin əlavə olunması məntiqini qeyd edirik.
<Window x:Class="FlexGridProject.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:local="clr-namespace:FlexGridProject"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Width="800"
Height="450"
Topmost="True"
WindowStyle="ToolWindow"
mc:Ignorable="d">
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<Border Name="ROOT"
MinWidth="150"
MinHeight="100"
HorizontalAlignment="Stretch"
Background="{Binding}">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="20"
FontWeight="Bold"
Foreground="White">
<Run Text="{Binding ElementName=ROOT, Path=ActualHeight, Mode=OneWay, StringFormat=000}" />
<Run Text="x" />
<Run Text="{Binding ElementName=ROOT, Path=ActualWidth, Mode=OneWay, StringFormat=000}" />
</TextBlock>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ItemsControl Name="ItemsContainer">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource ItemTemplate}"
MouseDown="ContentControl_MouseDown" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:FlexGrid ItemMinHeight="100" ItemMinWidth="150" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
Burada, elementlər üçün ItemTemplate adlı şablon təyin edirik və əvvəl qeyd etdiyimiz siyahını ItemsControl komponenti ilə əlaqələndiririk.
Nəticə
Nəticə olaraq intuitiv FlexGrid panelin sayəsində biz CSS Flexbox-a oxşar məntiqdən öz desktop proqramlarımızda istifadə edə bilərik.