0% found this document useful (0 votes)
85 views28 pages

Tech

This document contains code for a todo list application built with Avalonia. It includes code for the main window class that defines the UI and logic for the todo list. Key aspects include: - Defining events for adding, removing, and reordering card columns - Storing and updating the project info including card and tag data - Drag and drop support for reordering and moving cards between columns - Methods for creating, removing, and updating card columns

Uploaded by

Lili
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
85 views28 pages

Tech

This document contains code for a todo list application built with Avalonia. It includes code for the main window class that defines the UI and logic for the todo list. Key aspects include: - Defining events for adding, removing, and reordering card columns - Storing and updating the project info including card and tag data - Drag and drop support for reordering and moving cards between columns - Methods for creating, removing, and updating card columns

Uploaded by

Lili
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 28

Листинг 1.

Код файла Program


using Avalonia;
using System;

namespace TodoApp
{
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't
initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);

// Avalonia configuration, don't remove; also used by visual designer.


public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
}
Листинг 2. Код файла MainWindow
using Avalonia.Controls;
using Avalonia.Interactivity;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia;
using System;
using System.Linq;
using System.IO;
namespace TodoApp
{
public partial class MainWindow : Window
{
public delegate void NewColumnAddedEventHandler(CardColumnInfo info, int
id);
public event NewColumnAddedEventHandler? OnNewColumnAdded;

public delegate void ColumnRemovedEventHandler(int id);


public event NewColumnAddedEventHandler? OnColumnRemoved;

/// <summary>
/// Called when tags get updated by user
/// </summary>
/// <param name="tags"></param>
public delegate void TagsChangedEventHandler(List<TagInfo> tags);

public event TagsChangedEventHandler? OnTagsChanged;

private List<CardColumn> _columns = new List<CardColumn>();


private List<TagInfo> _tags = new List<TagInfo>();
private List<TagInfo> _displayedTags = new List<TagInfo>();

/// <summary>
/// Tags that are marked to be displayed
/// </summary>
public List<TagInfo> CurrentlyVisibleTags => _displayedTags;

private string? _currentProjectFilePath;

public string? CurrentProjectFilePath


{
get => _currentProjectFilePath;
set
{
_currentProjectFilePath = value;
Title = $"Todo : {_currentProjectFilePath ?? String.Empty}";
}
}

public List<TagInfo> Tags => _info.Tags;

private ProjectInfo _info;

private ColumnDragPreview? _previewDragDrop;

private bool _canDragColumn = false;

public bool CanDragColumn => _canDragColumn;

/// <summary>
/// Current info of the whole project
/// </summary>
/// <value></value>
public ProjectInfo Info
{
//TODO: Add auto update for column and card info based on events
get => _info;
}

/// <summary>
/// Removes card from it's current column and creates a new copy in the
destination column<para/>
/// Throws null reference exception if either column or card reference invalid
objects
/// </summary>
/// <param name="cardId">Id of the card that will be moved</param>
/// <param name="columnId">Destination column id</param>
/// <param name="preferredPosition">If not null card will be positioned at this
position, shifting every other card further down</param>
public void ChangeCardColumn(Guid cardId, Guid columnId, int?
preferredPosition)
{
CardColumn column = _columns.FirstOrDefault(c => c.HasCard(cardId)) ??
throw new NullReferenceException("No column has card with given id");
//technically this will never return null because we established that column
does indeed have needed card
//but just in case >_>
Card card = column.GetCard(cardId) ?? throw new
NullReferenceException("No card has given id in this column");
CardColumn dstColumn = _columns.FirstOrDefault(c => c.Id ==
columnId) ?? throw new NullReferenceException("No column has id of the
destination column");
dstColumn.AddNewCard(card.Info, preferredPosition);
column.RemoveCard(cardId);
_updateInfo();
}

/// <summary>
/// Moves columns in the window. This simply reorders columns
/// </summary>
/// <param name="columnId">Which column to move</param>
/// <param name="position">Which slot should the column occupy</param>
public void MoveCardColumn(Guid columnId, int? position)
{
CardColumn moved = _columns.FirstOrDefault(p => p.Id == columnId) ??
throw new NullReferenceException("Column with given id is not present in the
project");
_columns.Remove(moved);
ColumnPanel.Children.Remove(moved);
if (position == null)
{
_columns.Add(moved);
ColumnPanel.Children.Add(moved);
}
else
{
_columns.Insert(position.Value, moved);
ColumnPanel.Children.Insert(position.Value, moved);
}
}

/// <summary>
/// Removes card from it's current column and creates a new copy in the
destination column<para/>
/// Throws null reference exception if either column or card reference invalid
objects<para/>
/// This version uses relies on ever changing _columns array making it unreliable
and it will probably be deprecated in the future
/// </summary>
/// <param name="cardId">Id of the card that will be moved</param>
/// <param name="columnId">Index of the destination column in the column
array</param>
/// <param name="preferredPosition">If not null card will be positioned at this
position, shifting every other card further down</param>
[Obsolete("his version uses relies on ever changing _columns array making it
unreliable and it will probably be deprecated in the future")]
public void ChangeCardColumn(Guid cardId, int columnId, int?
preferredPosition)
{
CardColumn column = _columns.FirstOrDefault(c => c.HasCard(cardId)) ??
throw new NullReferenceException("No column has card with given id");
//technically this will never return null because we established that column
does indeed have needed card
//but just in case >_>
Card card = column.GetCard(cardId) ?? throw new
NullReferenceException("No card has given id in this column");
CardColumn dstColumn = _columns[columnId];
dstColumn.AddNewCard(card.Info);
column.RemoveCard(cardId);
_updateInfo();
}

public MainWindow()
{
InitializeComponent();
_info = new ProjectInfo();
_info.Tags.Add(new TagInfo(0, "Very important", TagColor.DarkRed, 2));
_info.Tags.Add(new TagInfo(1, null, TagColor.LightYellow, 1));
_info.Tags.Add(new TagInfo(2, null, TagColor.Blue, 0));

AddHandler(DragDrop.DragOverEvent, _dragOver);
AddHandler(DragDrop.DragLeaveEvent, _dragLeave);
AddHandler(DragDrop.DropEvent, _dragDrop);
}

/// <summary>
/// Bind events to the card and mark project as edited
/// </summary>
/// <param name="card"></param>
public void RegisterCard(Card card)
{
card.OnCardDragged += _cardBeginDrag;
card.OnCardFinishedDrag += _cardFinishedDrag;
}

private void _cardFinishedDrag(Card? card)


{
_canDragColumn = true;
}

private void _cardBeginDrag(Card? card)


{
_canDragColumn = false;
}

private void _dragOver(object? sender, DragEventArgs e)


{
if (e.Data.Contains(DataFormats.Text))
{
Console.WriteLine(e.Data.GetText());
ItemDragInfo? info =
Newtonsoft.Json.JsonConvert.DeserializeObject<ItemDragInfo>(e.Data.GetText() ??
throw new NullReferenceException("Invalid drag and drop data"));
if (_previewDragDrop != null || info.ItemType != DragItemType.Column)
{
return;
}
_previewDragDrop = new ColumnDragPreview();
ColumnPanel.Children.Add(_previewDragDrop);
}
}
private void _onColumnDraggedOverColumn(CardColumn column,
ItemDragInfo info)
{

if (info.ItemType != DragItemType.Column)
{
return;
}
int index = _columns.IndexOf(column);
if (_previewDragDrop != null)
{
ColumnPanel.Children.Remove(_previewDragDrop);
}
_previewDragDrop = new ColumnDragPreview() { Id = index };
ColumnPanel.Children.Insert(index, _previewDragDrop);
}

private void _dragLeave(object? sender, RoutedEventArgs e)


{
if (_previewDragDrop != null && !_previewDragDrop.IsPointerOver)
{
ColumnPanel.Children.Remove(_previewDragDrop);
_previewDragDrop = null;
}
}

private void _dragDrop(object? sender, DragEventArgs e)


{
if (_previewDragDrop != null)
{
ItemDragInfo? info =
Newtonsoft.Json.JsonConvert.DeserializeObject<ItemDragInfo>(e.Data.GetText() ??
throw new NullReferenceException("Invalid drag and drop data"));
if (_previewDragDrop != null && info.ItemType ==
DragItemType.Column)
{
MoveCardColumn(info.ItemId, _previewDragDrop.Id);
}
ColumnPanel.Children.Remove(_previewDragDrop);
_previewDragDrop = null;
}
}
private void _onNewColumnButtonClicked(object? sender, RoutedEventArgs e)
{
_addColumn(null);
}

/// <summary>
/// Creates new column using provided info or default column if info is null
/// </summary>
/// <param name="info"></param>
private void _addColumn(CardColumnInfo? info, int? position = null)
{
CardColumn column = new CardColumn();
column.Init(this, info);
column.OnColumnRemoved += _onColumnRemoved;
column.OnOtherColumnDraggedOver += _onColumnDraggedOverColumn;
if (position == null)
{
_columns.Add(column);
ColumnPanel.Children.Add(column);
}
else
{
_columns.Insert(position.Value, column);
ColumnPanel.Children.Insert(position.Value, column);
}
OnNewColumnAdded?.Invoke(column.Info, _columns.Count);
}

public async void BeginCardEdit(CardInfo info, Guid id)


{
CardEditWindow editDialog = new CardEditWindow();
editDialog.Init(info, this);
await editDialog.ShowDialog(this);
CardColumn column = _columns.FirstOrDefault(column =>
column.HasCard(id)) ?? throw new NullReferenceException("No column has given
card");
column.UpdateCard(id, editDialog.CardInfo);
}

private void _onColumnRemoved(CardColumn column)


{
_columns.Remove(column);
ColumnPanel.Children.Remove(column);
}

private void _onExitRequested(object? sender, RoutedEventArgs e)


{
Close();
}

private void _onNewRequested(object? sender, RoutedEventArgs e)


{
_clear();
}

/// <summary>
/// Regenerate column info by iterating over current columns
/// </summary>
private void _updateInfo()
{
List<CardColumnInfo> columns = new List<CardColumnInfo>();
foreach (CardColumn column in _columns)
{
columns.Add(column.Info);
}
_info.Columns = columns;
}

private async void _displayLabelEditWindow()


{
ProjectLabelsEditWindow dialog = new ProjectLabelsEditWindow();
dialog.Init(Tags, Info.CurrentTagId);
await dialog.ShowDialog(this);

//since tags were changed we have to notify all card of changed tags
OnTagsChanged?.Invoke(Tags);
Info.CurrentTagId = dialog.CurrentTagId;
}
private void _onDisplayLabelEditWindowPressed(object? sender,
RoutedEventArgs e)
{
_displayLabelEditWindow();
}

/// <summary>
/// Opens a file save menu and waits for user to pick a file
/// </summary>
/// <returns>Path to the opened file</returns>
private async System.Threading.Tasks.Task<string?> _getProjectFilePath()
{
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filters?.Add(new FileDialogFilter() { Name = "Todo Projects",
Extensions = { "todo" } });
return await dialog.ShowAsync(this);
}
private async void _onSaveRequested(object? sender, RoutedEventArgs e)
{
CurrentProjectFilePath ??= await _getProjectFilePath();
if (CurrentProjectFilePath == null)
{
return;
}
_updateInfo();
string info = Newtonsoft.Json.JsonConvert.SerializeObject(Info);
File.WriteAllText(CurrentProjectFilePath, info);
}

private async void _onSaveASRequested(object? sender, RoutedEventArgs e)


{
CurrentProjectFilePath = await _getProjectFilePath();
if (CurrentProjectFilePath == null)
{
return;
}
_updateInfo();
string info = Newtonsoft.Json.JsonConvert.SerializeObject(Info);
File.WriteAllText(CurrentProjectFilePath, info);
}

private async void _onOpenRequested(object? sender, RoutedEventArgs e)


{
CurrentProjectFilePath = await _getProjectFilePath();
if (CurrentProjectFilePath == null)
{
return;
}
string text = File.ReadAllText(CurrentProjectFilePath);
ProjectInfo info =
Newtonsoft.Json.JsonConvert.DeserializeObject<ProjectInfo>(text) ?? throw new
NullReferenceException("Unable to open project file");
_clear();
_loadFromFile(info);
}

private async void _onFilterRequested(object? sender, RoutedEventArgs e)


{
TagFilterDialog dialog = new TagFilterDialog();
dialog.Init(Tags, _displayedTags);
await dialog.ShowDialog(this);
foreach (CardColumn column in _columns)
{
column.FilterCards(dialog.DisplayedTags);
}
}

private void _clear()


{
//we have to make a copy because original list is going to be modified in the
loop
CardColumn[] backup = new CardColumn[_columns.Count];
_columns.CopyTo(0, backup, 0, _columns.Count);
foreach (CardColumn column in backup)
{
_onColumnRemoved(column);
}
}

/// <summary>
/// Writes info from project info to columns and sets given info as current info
/// </summary>
/// <param name="info">Project info to make current</param>
private void _loadFromFile(ProjectInfo info)
{
_info = info;
foreach (CardColumnInfo columnInfo in info.Columns)
{
_addColumn(columnInfo);
}
}
}
}
Листинг 3. Код файла App.xaml.cs
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;

namespace TodoApp
{
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()


{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}

base.OnFrameworkInitializationCompleted();
}
}
}
Листинг 4. Код файла MainWindow.xaml
<Window
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:todo="clr-namespace:TodoApp"
mc:Ignorable="d" d:DesignWidth="50"
Width="1000"
d:DesignHeight="50"
x:Class="TodoApp.MainWindow"
Title="Todo App"
DragDrop.AllowDrop="True">
<Window.ContextMenu>
<ContextMenu>
<MenuItem Header="Add column"
Click="_onNewColumnButtonClicked" />
</ContextMenu>
</Window.ContextMenu>
<Grid>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_New" Click="_onNewRequested" />
<MenuItem Header="_Open" Click="_onOpenRequested" />
<MenuItem Header="_Save" Click="_onSaveRequested" />
<MenuItem Header="Save _as" Click="_onSaveASRequested" />
<Separator />
<MenuItem Header="_Quit" Click="_onExitRequested" />
</MenuItem>
<MenuItem Header="_Project">
<MenuItem Header="Edit _Labels"
Click="_onDisplayLabelEditWindowPressed" />
</MenuItem>
<MenuItem Header="_Card filter"
ToolTip.Tip="Open the menu for selecting labels that should be
displayed"
Click="_onFilterRequested"
/>
</Menu>
<DockPanel Background="#0079bf" LastChildFill="False">
<Button Click="_onNewColumnButtonClicked" Background="White"
DockPanel.Dock="Top">Add new column</Button>
<ScrollViewer Width="{Binding $parent[Window].Width}"
HorizontalScrollBarVisibility="Visible">
<StackPanel x:Name="ColumnPanel" Orientation="Horizontal"
HorizontalAlignment="Left"></StackPanel>
</ScrollViewer>
</DockPanel>
</DockPanel>
</Grid>
</Window>
Листинг 5. Код файла App.xaml
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="TodoApp.App">
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>

Листнг 6. TagFilterDialog.axaml.cs
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;

namespace TodoApp
{
public partial class TagFilterDialog : Window
{
public ObservableCollection<LabelCheckButton> Labels { get; } = new
ObservableCollection<LabelCheckButton>();
private List<TagInfo>? _displayedTags;
/// <summary>
/// Tags that user has selected to be displayed<para/>
/// Null if user chose to select all
/// </summary>
public List<TagInfo>? DisplayedTags => _displayedTags;
public TagFilterDialog()
{
InitializeComponent();
this.DataContext = this;
}

private void _onResetFilterPressed(object? sender, RoutedEventArgs e)


{
_displayedTags = null;
foreach (LabelCheckButton btn in Labels)
{
btn.EnabledCheckBox.IsChecked = false;
}
}

private void _onTagToggled(TagInfo info, bool enabled)


{
_displayedTags ??= new List<TagInfo>();
if (enabled)
{

_displayedTags.Add(info);
}
else
{
_displayedTags.Remove(info);
}
}

public void Init(List<TagInfo> tags, List<TagInfo>? displayedTags)


{
foreach (TagInfo tag in tags)
{
LabelCheckButton btn = new LabelCheckButton();
Labels.Add(btn);
btn.OnTagToggled += _onTagToggled;
btn.EnabledCheckBox.IsChecked = displayedTags?.Contains(tag) ?? false;
btn.Init(tag);
}
_displayedTags = displayedTags;
}
}
}
Листинг 7. TagFilterDialog.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" MinHeight="150" Height=" 500" MinWidth = "100" Width =
"300"
x:Class="TodoApp.TagFilterDialog"
Title="TagFilterDialog">
<ScrollViewer Height="500">
<DockPanel>
<Button Click="_onResetFilterPressed" DockPanel.Dock = "Top">Reset
filter</Button>
<ItemsControl Items="{Binding Labels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="1" DockPanel.Dock = "Bottom"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>
</ScrollViewer>
</Window>

Листинг 8. TagInfo.cs
using Avalonia.Media;

public enum TagColor


{
IndianRed,
DarkRed,
Yellow,
LightYellow,
Blue,
LightBlue,
DarkBlue,
DarkGreen,
DarkOliveGreen,
DarkSeaGreen,
ForestGreen,
LightGreen,
LightGray
}
public class TagInfo
{
/// <summary>
/// Index of the tag
/// </summary>
public uint Id;
public string? Text;
public TagColor Color;

public int Priority;

public TagInfo(uint id, string? text, TagColor color, int priority)


{
Id = id;
Text = text;
Color = color;
Priority = priority;
}

public TagInfo() { }
}
Листинг 9. ProjectInfo.cs
using System;
using System.Collections.Generic;

public class ProjectInfo


{
public uint CurrentTagId = 0;
public List<CardColumnInfo> Columns = new List<CardColumnInfo>();

public List<TagInfo> Tags = new List<TagInfo>();

public ProjectInfo(List<CardColumnInfo> columns)


{
Columns = columns;
}

/// <summary>
/// Creates an empty project
/// </summary>
public ProjectInfo()
{
}
}
Листинг 10. CardInfo.cs
using System.Collections.Generic;
using System;
/// <summary>
/// Contains all of the information about card itself
/// </summary>
public class CardInfo
{
public string Name = string.Empty;
/// <summary>
/// Optional description of the card
/// </summary>
public string? Description;
public List<uint> Tags = new List<uint>();
public DateTimeOffset? DueDate;
public bool HasDueDate;

public CardInfo(string name, string? description, List<uint> tags, DateTimeOffset?


dueDate, bool hasDueDate)
{
Name = name;
Description = description;
Tags = tags;
DueDate = dueDate;
HasDueDate = hasDueDate;
}
}
Листинг 11. CardColumnInfo.cs
using System;
using System.Collections.Generic;

public class CardColumnInfo


{
public string Name;
public List<CardInfo> Cards;

public CardColumnInfo(string name, List<CardInfo> cards)


{
Name = name;
Cards = cards;
}
}
Листинг 12. ProjectLabelsEditWindow.axaml.cs
using Avalonia;
using Avalonia.Media;
using Avalonia.Controls;
using Avalonia.Interactivity;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace TodoApp
{
public partial class ProjectLabelsEditWindow : Window
{
private List<TagInfo> _tags = new List<TagInfo>();
private ObservableCollection<ListBoxItem> _tagItems = new
ObservableCollection<ListBoxItem>();
private uint _currentTagId = 0;
public uint CurrentTagId => _currentTagId;

public ObservableCollection<ListBoxItem> Items


{
get => _tagItems;
set => _tagItems = value;
}

/// <summary>
/// All of the tags that this window worked with, including unchanged ones
/// </summary>
public List<TagInfo> Tags => _tags;
private int _selectedLabel = -1;
public ProjectLabelsEditWindow()
{
InitializeComponent();
this.DataContext = this;
}

/// <summary>
/// Currently selected tag, meant for binding with the UI<para/>
/// Setting this value will cause tag info to be updated
/// </summary>
public int LabelSelection
{
get => _selectedLabel;
set
{
_selectedLabel = value;
_displayTagEdit();
//_tags[value].Text = string.IsNullOrWhiteSpace(NameBox.Text) ? null :
NameBox.Text;
}
}

private async void _displayTagEdit()


{
if (LabelSelection <= -1 || LabelSelection >= _tags.Count)
{
return;
}
LabelPropsEditWindow editDialog = new LabelPropsEditWindow();

editDialog.Init(_tags[LabelSelection], true);
await editDialog.ShowDialog(this);
if (editDialog.WasRemoved)
{
_tags.RemoveAt(LabelSelection);
}
else if (editDialog.Info != null)
{
_tags[LabelSelection] = editDialog.Info;
}
Init(_tags, _currentTagId);
}

private async void _displayTagCreation()


{
LabelPropsEditWindow editDialog = new LabelPropsEditWindow();
editDialog.Init(new TagInfo(), false);
await editDialog.ShowDialog(this);
if (editDialog.Info != null)
{
editDialog.Info.Id = ++_currentTagId;
_tags.Add(editDialog.Info);
}
Init(_tags, _currentTagId);
}

private void _onAddNewLabelClicked(object? sender, RoutedEventArgs e)


{
_displayTagCreation();
}
public void Init(List<TagInfo> tags, uint currentTagId)
{
_tagItems.Clear();
_tags = tags;
foreach (TagInfo tag in tags)
{
ListBoxItem item = new ListBoxItem();
item.Content = new Border()
{
Background = new SolidColorBrush(Color.Parse(tag.Color.ToString())),
MinWidth = 15,
MinHeight = double.IsNaN(item.Height) ? 40 : item.Height,
Child = new TextBlock() { Text = tag.Text ?? " " }
};
_tagItems.Add(item);
}
}
}
}
Листинг 13. ProjectLabelsEditWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="TodoApp.ProjectLabelsEditWindow"
MaxWidth="400"
Title="Edit project labels(tags)">
<DockPanel Margin="4">
<TextBox Watermark="Search labels" DockPanel.Dock="Top"></TextBox>
<Button DockPanel.Dock="Top" Click = "_onAddNewLabelClicked">Add new
label</Button>
<TextBlock DockPanel.Dock="Top">Tags - Click on the tag to edit</TextBlock>
<ListBox x:Name="LabelOptions" Items="{Binding Items}"
SelectedIndex="{Binding LabelSelection}" DockPanel.Dock="Top"></ListBox>
</DockPanel>
</Window>

Листинг 14. LabelPropsEditWindow.axaml.cs


using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Interactivity;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace TodoApp
{
/// <summary>
/// Window that allows changing properties for the tags<para/>
/// </summary>
public partial class LabelPropsEditWindow : Window
{
private TagInfo? _info;
/// <summary>
/// Current value of the info including the edits<para/>
/// If value is null then it means that no edits should be written
/// </summary>
public TagInfo? Info
{
get => _info;
set => _info = value;
}

public bool WasRemoved = false;


private ObservableCollection<ListBoxItem> _colors = new
ObservableCollection<ListBoxItem>();

public ObservableCollection<ListBoxItem> Colors => _colors;


public LabelPropsEditWindow()
{
InitializeComponent();
this.DataContext = this;
foreach (TagColor color in Enum.GetValues(typeof(TagColor)))
{
_colors.Add(new ListBoxItem()
{
Background = new SolidColorBrush(Color.Parse(color.ToString())),
Content = new TextBlock() { Text = " " },
Margin = new Thickness(5.0)
});
}
}

private void _onSaveClicked(object? sender, RoutedEventArgs e)


{
Close();
}
private void _onRemoveClicked(object? sender, RoutedEventArgs e)
{
WasRemoved = true;
Close();
}
private void _onEditCancelClicked(object? sender, RoutedEventArgs e)
{
//If edit was canceled then we set value to null to avoid overriding previous
info
_info = null;
Close();
}

public int SelectedColorIndex


{
get => (int)(_info?.Color ?? TagColor.IndianRed);
set
{
if (_info != null && _colors[(int)(_info?.Color ??
TagColor.IndianRed)].Content is TextBlock oldText)
{
oldText.Text = " ";
}
if (_colors[value].Content is TextBlock text)
{
text.Text = "Selected";
}
_info.Color = (TagColor)(value);
}
}

public int TagPriority


{
get => _info?.Priority ?? 0;
set => _info.Priority = value;
}

public string TagText


{
get => _info?.Text ?? string.Empty;
set => _info.Text = value;
}

public string StateTextSave => !_editMode ? "Create" : "Save";


public bool EditMode => _editMode;
private bool _editMode = false;

public void Init(TagInfo info, bool editMode)


{
_editMode = editMode;
_info = info;
NameBox.Text = info.Text ?? string.Empty;
SaveButton.Content = StateTextSave;
PrioritySelectBox.Value = info.Priority;
RemoveButton.IsVisible = EditMode;
}
}
}

Листинг 15. LabelPropsEditWindow.axaml


<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
MinWidth="200"
MinHeight="300"
MaxWidth="400"
MaxHeight="500"
x:Class="TodoApp.LabelPropsEditWindow"
Title="LabelPropsEditWindow">

<StackPanel x:Name="EditControls" DockPanel.Dock="Bottom">


<TextBlock>Name</TextBlock>
<TextBox x:Name="NameBox" Text="{Binding TagText}"></TextBox>
<TextBlock>Priority</TextBlock>
<NumericUpDown x:Name="PrioritySelectBox" Value="{Binding
TagPriority}"></NumericUpDown>
<TextBlock>Color</TextBlock>
<ScrollViewer Height="300">
<ListBox Items="{Binding Colors}" SelectedIndex="{Binding
SelectedColorIndex}" Margin="5" Padding="5" x:Name="ColorSelector" />
</ScrollViewer>
<DockPanel LastChildFill="false" Margin="10">
<Button DockPanel.Dock="Left" Background="DarkGreen" Content="{Binding
StateTextSave}" Click="_onSaveClicked" x:Name="SaveButton" />
<Button DockPanel.Dock="Left" Background="Red" IsVisible="{Binding
EditMode}" x:Name="RemoveButton"
Click="_onRemoveClicked">Remove</Button>
<Button DockPanel.Dock="Right" Background="IndianRed"
Click="_onEditCancelClicked">Cancel</Button>
</DockPanel>
</StackPanel>
</Window>

Листинг 16. LabelCheckButton.axaml.cs


using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Interactivity;

namespace TodoApp
{
public partial class LabelCheckButton : UserControl
{
public delegate void TagToggledEventHandler(TagInfo info, bool enabled);

public event TagToggledEventHandler? OnTagToggled;

TagInfo _info;

public uint TagName => _info.Id;

public bool Enabled


{
get => EnabledCheckBox.IsChecked ?? false;
set
{
EnabledCheckBox.IsChecked = value;
}
}
public LabelCheckButton()
{
InitializeComponent();
}

public void Init(TagInfo info)


{
_info = info;
Background.Background = new
SolidColorBrush(Color.Parse(info.Color.ToString()));
TextLabel.Text = info.Text ?? string.Empty;
}
private void _onCheckBoxChanged(object? sender, RoutedEventArgs e)
{
// if we someone managed to change checked state to null then just treat it as
false
OnTagToggled?.Invoke(_info, EnabledCheckBox.IsChecked ?? false);
}
}
}

Листинг 17. LabelCheckButton.axaml


<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="TodoApp.LabelCheckButton">
<Border x:Name = "Background" Background="#4287f5">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="EnabledCheckBox" Checked="_onCheckBoxChanged"
Unchecked="_onCheckBoxChanged"/>
<TextBlock x:Name="TextLabel">PLS ADD LABEL NAME</TextBlock>
</StackPanel>
</Border>
</UserControl>

Листинг 18. CardEditWindow.axaml.cs


using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using System.Collections.Generic;
using System;
using Avalonia.Data;
using System.Linq;

namespace TodoApp
{
public partial class CardEditWindow : Window
{
public CardEditWindow()
{
InitializeComponent();
this.DataContext = this;
}
private List<LabelCheckButton> _tagButtons = new
List<LabelCheckButton>();

private CardInfo? _cardInfo;

public CardInfo? CardInfo => _cardInfo;

private string _name;

public string CardName


{
get => _cardInfo?.Name ?? string.Empty;
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new DataValidationException("Card name can not be empty");
}
else if (_cardInfo != null)
{
_cardInfo.Name = value;
}

}
}

public DateTimeOffset? CardDate


{
get => _cardInfo?.DueDate;
set { if (_cardInfo != null) _cardInfo.DueDate = value; }
}

public string CardDescription


{
get => _cardInfo?.Description ?? string.Empty;
set { if (_cardInfo != null) _cardInfo.Description = value; }
}

public bool HasDueDate


{
get => _cardInfo?.HasDueDate ?? false;
set { if (_cardInfo != null) _cardInfo.HasDueDate = value; }
}

public void Init(CardInfo info, MainWindow window)


{
if (info != null)
{
_cardInfo = info;
_name = info.Name;
CardNameTextBox.Text = info.Name;
CardDescriptionTextBox.Text = info.Description;
DueDatePicker.SelectedDate = info.DueDate;
EnableDatePickerCheckBox.IsChecked = info.HasDueDate;
foreach (LabelCheckButton btn in _tagButtons.Where(p =>
info.Tags.Contains(p.TagName)))
{
btn.Enabled = true;
}
foreach (LabelCheckButton btn in _tagButtons.Where(p => !
info.Tags.Contains(p.TagName)))
{
btn.Enabled = false;
}
UpdateTags(window.Tags);
}
else
{
Close();
}
}

/// <summary>
/// Regenerates tag list based on new tags
/// </summary>
/// <param name="tags">New tags</param>
public void UpdateTags(List<TagInfo> tags)
{
TagListPanel.Children.Clear();
_tagButtons.Clear();
foreach (TagInfo tag in tags)
{
LabelCheckButton btn = new LabelCheckButton();
TagListPanel.Children.Add(btn);
_tagButtons.Add(btn);
btn.Enabled = CardInfo?.Tags.Contains(tag.Id) ?? false;
btn.OnTagToggled += _onTagToggled;
btn.Init(tag);
}
}
private void _onTagToggled(TagInfo info, bool enabled)
{
if (enabled)
{
_cardInfo?.Tags.Add(info.Id);
}
else
{
_cardInfo?.Tags.Remove(info.Id);
}
}

private void _clear()


{
_name = string.Empty;
CardDescriptionTextBox.Text = string.Empty;
}
}
}

Листинг 19. CardEditWindow.axaml


<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" MinHeight="300" MinWidth="300" MaxWidth="350"
MaxHeight="550"
x:Class="TodoApp.CardEditWindow"
Title="Card editing">
<StackPanel Margin="5,5,5,5">
<TextBlock>Card Text</TextBlock>
<ScrollViewer Height="50">
<TextBox x:Name="CardNameTextBox" DockPanel.Dock="Left"
TextWrapping="Wrap" MaxWidth="350" Text="{Binding CardName}" />
</ScrollViewer>
<TextBlock>Description</TextBlock>
<ScrollViewer Height="200">
<TextBox x:Name="CardDescriptionTextBox" AcceptsReturn="True"
TextWrapping="Wrap" MaxWidth="350" Text="{Binding
CardDescription}">Description</TextBox>
</ScrollViewer>
<TextBlock>Card Text</TextBlock>
<StackPanel>
<DatePicker Name="DueDatePicker" IsEnabled="{Binding
#EnableDatePickerCheckBox.IsChecked}" SelectedDate="{Binding CardDate}" />
<CheckBox Name="EnableDatePickerCheckBox" IsChecked="{Binding
HasDueDate}">Has due date</CheckBox>
</StackPanel>
<TextBlock>Tags</TextBlock>
<ScrollViewer Height="250">
<StackPanel x:Name="TagListPanel" />
</ScrollViewer>
</StackPanel>
</Window>

You might also like