Возможно ли сделать конвертер для изображений .NET MAUI
Перед уточнением вопроса вот немного вводных данных:
- В workbench MYSQL есть бд с таблицей Installation.
- В этой таблице есть столбец Image, в котором будет хранится полный путь к изображению либо его название имеющее тип данных string.
- Я не использую MVVM подход и хотелось бы решение не в таком подходе.
- В мобильном приложении есть статьи, в которых отображаются данные из этой таблицы. Отображаются они так, что изображение слева, а данные справа.
Чтобы добавлять статьи у меня есть отдельная страница в приложении где я ввожу данные и буду вводить полный путь изображение или его url ссылку, который будет храниться в бд,так вот вопрос, как реализовать конвертер, который бы брал текстовые данные из поля Image находящегося в таблице Installation и конвертировал бы эти данные в изображение.Перерыл весь интернет и не нашел способа его реализовать, там в основном все делали через запросы к серверу,но у меня нету отдельного сервера в котором я мог бы его хранить,я использую обычный localhost. Не судите строго,я не так давно изучаю maui. Вот как выглядит структура таблицы Installation
public class Installation
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string NameOs { get; set; }
public string WhereInstall { get; set; }
public string ImageUri {get;set; }
public DateTime Create_dt { get; set; }
public DateTime? Update_dt { get; set; }
public int TypeId { get; set; } = 4;
public TypeStatia TypeStatias { get; set; }
public bool ShowAdminButtons { get; set; } = true;
public Installation() { }
public Installation(
string title,
string description,
string nameOs,
string whereInstall)
{
Title = title;
Description = description;
NameOs = nameOs;
WhereInstall = whereInstall;
Create_dt = DateTime.Now;
Update_dt = DateTime.Now;
}
}
код страницы, которая отображает статьи с данными из таблицы Installation:
using IbragimovLinux.DatabaseContext;
using IbragimovLinux.Entities;
using IbragimovLinux.ValidationPages;
namespace IbragimovLinux.Windows;
public partial class InstallationWindow : ContentPage
{
public InstallationWindow()
{
InitializeComponent();
}
protected override void OnAppearing()
{
RefreshCollectionView();
LoadData();
var roleId = ApplicationData.CurrentUser!.Id;
CheckRolePermissions();
}
private void CheckRolePermissions()
{
bool isAdmin = ApplicationData.CurrentUser?.RoleId == 1;
AddButton.IsVisible = isAdmin;
}
private void LoadData()
{
using (var db = new ApplicationDbContext())
{
var install = db.Installations.ToList();
if (ApplicationData.CurrentUser?.RoleId != 1)
{
foreach (var item in install)
{
item.ShowAdminButtons = false;
}
}
InstallationLV.ItemsSource = install;
}
}
private void AddButton_Clicked(object sender, EventArgs e)
{
AppShell.Current.GoToAsync(nameof(AddInfo), true);
}
private async void Delete_Clicked(object sender, EventArgs e)
{
if (ApplicationData.CurrentUser?.RoleId != 1)
{
await DisplayAlert("Ошибка", "Недостаточно прав для удаления", "OK");
return;
}
// Получаем статью для удаления
var button = (ImageButton)sender;
var article = (Installation)button.BindingContext;
// Запрашиваем подтверждение
bool confirm = await DisplayAlert(
"Удаление статьи",
$"Вы уверены, что хотите удалить статью \"{article.Title}\"?",
"Да", "Нет");
if (confirm)
{
try
{
// Удаляем из базы данных
using (var db = new ApplicationDbContext())
{
db.Installations.Remove(article);
await db.SaveChangesAsync();
LoadData();
}
await DisplayAlert("Успех", "Статья удалена", "OK");
}
catch (Exception ex)
{
await DisplayAlert("Ошибка", $"Не удалось удалить статью: {ex.Message}", "OK");
}
}
}
private void EditButton_Clicked(object sender, EventArgs e)
{
AppShell.Current.GoToAsync(nameof(EditPage), true);
}
private void RefreshData(object sender, EventArgs e)
{
RefreshCollectionView();
RefreshLV.IsRefreshing = false;
}
private void RefreshCollectionView()
{
ApplicationDbContext dbContext = new ApplicationDbContext();
InstallationLV.ItemsSource = dbContext.Installations.ToList();
}
private async void Tap_event(object sender, TappedEventArgs e)
{
if (sender is Border border && border.BindingContext is Installation installation)
{
DataTransfer.CurrentId = installation.Id;
DataTransfer.CurrentType = installation.TypeId;
await Navigation.PushAsync(new DetailPage());
}
}
}
Xaml часть этой страницы:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="IbragimovLinux.Windows.InstallationWindow"
xmlns:entities="clr-namespace:IbragimovLinux.Entities"
Title="Установка"
BackgroundColor="LightGrey">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<!-- Кнопки "Выйти" и "Добавить" -->
<RowDefinition Height="Auto"/>
<!-- Заголовок таблицы -->
<RowDefinition Height="*"/>
<!-- Список данных -->
</Grid.RowDefinitions>
<!--<HorizontalStackLayout Grid.Row="0" Spacing="10" Padding="10">
<SearchBar
x:Name="SearchBar"
Placeholder="Search articles..."
WidthRequest="250"
HorizontalOptions="StartAndExpand"/>-->
<HorizontalStackLayout
Grid.Row="1"
Padding="10"
HorizontalOptions="End">
<ImageButton
x:Name="AddButton"
Source="addbutton.png"
WidthRequest="30"
HeightRequest="30"
Clicked="AddButton_Clicked"
IsVisible="{Binding ShowAdminButtons}"/>
</HorizontalStackLayout>
<!-- RefreshView для обновления списка -->
<RefreshView
Grid.Row="2"
x:Name="RefreshLV"
Refreshing="RefreshData">
<ScrollView>
<CollectionView x:Name="InstallationLV">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type entities:Installation}">
<Border
StrokeThickness="0.5"
Stroke="Black"
Padding="10,15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.RowSpan="3"
Source="D:\Ibra\IbragimovLinux\IbragimovLinux\Resources\Images\dotnetbot.png"
WidthRequest="60"
HeightRequest="60"
Aspect="AspectFill"/>
<Label
Grid.Column="1"
Grid.Row="0"
Text="{Binding Title}"
MaxLines="1"
LineBreakMode="TailTruncation"
FontSize="18"
FontAttributes="Bold"
TextColor="Black"/>
<Label
Grid.Column="1" Grid.Row="1"
FontSize="14"
TextColor="Blue"
Text="{Binding Create_dt}"/>
<Label
Grid.Column="1"
Grid.Row="2"
FontSize="16"
TextColor="#333333"
Text="{Binding Description, StringFormat='Описание: {0}'}"
LineBreakMode="TailTruncation"
MaxLines="3"/>
<HorizontalStackLayout Grid.Column="2" Grid.RowSpan="3"
VerticalOptions="Center"
Spacing="10"
IsVisible="{Binding ShowAdminButtons}">
<ImageButton
Source="delete.png"
WidthRequest="25"
HeightRequest="20"
Clicked="Delete_Clicked" />
<ImageButton
Source="write.png"
WidthRequest="25"
HeightRequest="20"
Clicked="EditButton_Clicked"/>
</HorizontalStackLayout>
</Grid>
<Border.GestureRecognizers>
<TapGestureRecognizer Tapped="Tap_event" CommandParameter="{Binding}"/>
</Border.GestureRecognizers>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ScrollView>
</RefreshView>
</Grid>
</ContentPage>
Код страницы добавления:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="IbragimovLinux.ValidationPages.AddInfo"
Title="Добавление статьи">
<ScrollView>
<StackLayout Padding="20" Spacing="10">
<!-- ComboBox для выбора таблицы -->
<Picker x:Name="TablePicker" Title="Выберите таблицу" HorizontalOptions="FillAndExpand">
<Picker.Items>
<x:String>Codes</x:String>
<x:String>Errors</x:String>
<x:String>Installations</x:String>
<x:String>Fixes</x:String>
<x:String>Updates</x:String>
</Picker.Items>
</Picker>
<!-- Динамические поля для ввода данных -->
<StackLayout x:Name="InputFieldsLayout" Spacing="10"/>
<!-- Кнопка для добавления данных -->
<Button Text="Добавить данные" Clicked="AddDataButton_Clicked" Grid.Row="2"/>
</StackLayout>
</ScrollView>
</ContentPage>
Xaml.cs часть страницы:
using System.Xml.Linq;
using IbragimovLinux.DatabaseContext;
using Microsoft.EntityFrameworkCore;
using IbragimovLinux.Entities;
using System;
namespace IbragimovLinux.ValidationPages;
public partial class AddInfo : ContentPage
{
private ApplicationDbContext _db = new();
private Dictionary<string, Entry> _inputFields = new Dictionary<string, Entry>(); // Поля для ввода данных
public AddInfo()
{
InitializeComponent();
TablePicker.SelectedIndexChanged += TablePicker_SelectedIndexChanged;
}
private void TablePicker_SelectedIndexChanged(object sender, EventArgs e)
{
var selectedTable = TablePicker.SelectedItem as string;
if (string.IsNullOrEmpty(selectedTable))
return;
// Очистка предыдущих полей
InputFieldsLayout.Children.Clear();
_inputFields.Clear();
// Создание полей для ввода данных в зависимости от выбранной таблицы
switch (selectedTable)
{
case "Codes":
AddInputField("Title", "Название");
AddInputField("Description", "Описание");
AddInputField("NameOs", "Операционная система");
break;
case "Errors":
AddInputField("Title", "Название ошибки");
AddInputField("Description", "Описание");
break;
case "Installations":
AddInputField("Title", "Название");
AddInputField("Description", "Описание");
AddInputField("NameOs", "Название Установленной ОС");
AddInputField("WhereInstall", "Способ установки");
break;
case "Fixes":
AddInputField("Title", "Название");
AddInputField("Description", "Описание");
AddInputField("ErrorName", "Название ошибки");
break;
case "Updates":
AddInputField("Title", "Название");
AddInputField("Description", "Описание");
break;
}
}
// Добавление поля для ввода данных
private void AddInputField(string fieldName, string labelText)
{
var label = new Label { Text = labelText };
var entry = new Entry();
InputFieldsLayout.Children.Add(label);
InputFieldsLayout.Children.Add(entry);
_inputFields[fieldName] = entry;
}
// Обработчик кнопки "Добавить данные"
private async void AddDataButton_Clicked(object sender, EventArgs e)
{
var selectedTable = TablePicker.SelectedItem as string;
if (string.IsNullOrEmpty(selectedTable))
{
await DisplayAlert("Ошибка", "Выберите таблицу", "OK");
return;
}
try
{
switch (selectedTable)
{
case "Codes":
var code = new Code(
_inputFields["Title"].Text,
_inputFields["Description"].Text,
_inputFields["NameOs"].Text);
_db.Codes.Add(code);
break;
case "Errors":
var error = new Error
{
Title = _inputFields["Title"].Text,
Description = _inputFields["Description"].Text,
};
_db.Errors.Add(error);
break;
case "Fixes":
var fix = new Fix
{
Title = _inputFields["Title"].Text,
Description = _inputFields["Description"].Text,
ErrorName = _inputFields["ErrorName"].Text,
DateAdd = DateTime.Now,
DateUpdate = DateTime.Now,
};
_db.Fixes.Add(fix);
break;
case "Installations":
var installation = new Installation
{
Title = _inputFields["Title"].Text,
Description = _inputFields["Description"].Text,
NameOs = _inputFields["NameOs"].Text,
WhereInstall = _inputFields["WhereInstall"].Text,
Create_dt = DateTime.Now,
Update_dt = DateTime.Now,
};
_db.Installations.Add(installation);
break;
case "Updates":
var update = new Update
{
Title = _inputFields["Title"].Text,
Description = _inputFields["Description"].Text,
DateUpdate = DateTime.Now
};
_db.Updates.Add(update);
break;
}
await _db.SaveChangesAsync();
await DisplayAlert("Успех", "Данные успешно добавлены", "OK");
// Очистка полей после добавления
foreach (var entry in _inputFields.Values)
{
entry.Text = string.Empty;
}
}
catch (Exception ex)
{
string errorMessage = $"Ошибка: {ex.Message}";
if (ex.InnerException != null)
{
errorMessage += $"\nInner: {ex.InnerException.Message}";
}
await DisplayAlert("Ошибка", errorMessage, "OK");
}
}
}
Вот фото как это выглядит сейчас:
Страница добавления,но тут я еще не сделал возможность добавления изображения(если, что это старый их вид):
Просто хочется попробовать сделать страницу статей в таком виде(см. картинку ниже),но все уперлось в то, как реализовать возможность пользователю добавлять изображения в статьи:
Буду рад хоть каким-то идеям как это можно реализовать или хотябы ссылки на похожую статью. Ибо я не смог найти
Ответы (1 шт):
Очень странный вопрос, который я даже не понял... Но раз просят показать пример, чтож, покажу... Единственное, это будет WPF проект, ибо ставить MAUI не хочу, а с WPF они схожи за исключением ряда моментов и компонентов. Пример будет максимально простым, без базы и чего-либо еще (это уж сами).
Возьму ваш класс
Installation, вырежу из него лишнее и создам локальный список с 20 случайно сгенерированными данными (библиотекаBogus). Это будет неким аналогом базы данных.Создам в XAML простейший шаблон отображения, все +- тоже самое, что и у вас, за исключением того, что
CollectionViewв WPF зоветсяItemsControl. Получаем такое:<ScrollViewer> <ItemsControl x:Name="itemsControl"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="5"> <Grid.ColumnDefinitions> <ColumnDefinition Width="100" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Image Grid.Column="0" Source="{Binding ImageUri}" /> <StackPanel Grid.Column="1" Margin="5 0 0 0"> <TextBlock Text="{Binding Title}" FontWeight="Medium" /> <TextBlock Text="{Binding Description}" TextWrapping="Wrap" /> </StackPanel> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer>Как видите, я задал
x:Name, как и вы... Не буду использовать привязки правильно, буду дергать контролы через код. Собственно весь код будет такой:public class Installation { public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } public string ImageUri { get; set; } } public partial class MainWindow : Window { public List<Installation> Installations { get; set; } = new List<Installation>(); public MainWindow() { InitializeComponent(); for (var i = 0; i < 20; i++) { var installation = new Faker<Installation>() .RuleFor(o => o.Id, f => i) .RuleFor(o => o.Title, f => f.Lorem.Sentence(3)) .RuleFor(o => o.Description, f => f.Lorem.Paragraph(2)) .RuleFor(o => o.ImageUri, f => f.Image.PicsumUrl()) .Generate(); Installations.Add(installation); } itemsControl.ItemsSource = Installations; } }
Запускаем и смотрим результат:
Как видите, без каких либо конверторов все отобразилось, почти все картинки (там проблема генерации и сайта), в ImageUri находится просто ссылка на картинку (например: https://picsum.photos/640/480/?image=650). Сама ссылка может быть и на локальный файл (скажем, рядом с проектом будет папка Images и там image1.jpg, значит ссылка будет /images/image1.jpg).
Теперь предположим, что нам надо изменить картинку по кнопке. Я с вашего позволения сделаю изменение на случайную.
Добавляю кнопку (у меня под описанием будет).
<Button Content="Изменить изображение" Click="Button_Click"/>В обработчике клика получаем текущий объект и задаем новый путь до картинки. Так, как мы кастыльно делаем все, без привязок, то ищем "Сендера" (тот, кто отправил событие) и берем его
DataContext(источник данных, который в MAUI зоветсяBindingContext), это и будет привязанный класс.private void Button_Click(object sender, RoutedEventArgs e) { var button = (sender as Button); var data = button?.DataContext as Installation; var fakeImage = new Faker().Image.PicsumUrl(); if (data is null) return; data.ImageUri = fakeImage; }Запускаем и... Ничего не происходит, хотя по отладке данные меняются. Все дело в том, что интерфейс надо оповестить, о том, что данные изменились. За это отвечает интерфейс
INotifyProprtyChanged, который надо реализовать изменяемому классу, и метод которого надо вызвать у изменяемого свойства. Способов реализовать это полно, в интернете найдете удобный для вас. А я использую современный подход с использованием библиотекиCommunityToolkit.MVVM, который за меня сгенерирует все нужное. Класс тогда превратиться в такой:public partial class Installation : ObservableObject { public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } [ObservableProperty] private string _imageUri; }
Запускаем и смотрим результат:
Как видите, все работает, без конвертеров и чего-либо еще. Простая работа с данными, простое изменение ссылки.
Для полноты картины расскажу как правильно делать все это.
Задаем окну источник данных на один конкретный класс (обычно его называют
MainViewModelили что-то такое. Да да, MVVM. Его не стоит бояться, его обязательно нужно учить с самого начала! В MAUI за источник данных отвечает свойствоBindingContext, значит и пишемBindingContext = new MainViewModel();.Данные для привязки (коллекцию) делаем публичным свойством. В моем примере это уже сделано.
Удаляем
itemsControl.ItemsSource = Installations;и переносим это в XAML, прописав там<... ItemsSource = "{Binding Installations}">. Также удаляемx:Name(в нормальном проекте его быть не должно, только если в пределах XAML используется).Clickменяем на команду (Command), привязав к свойству с типомICommand. Также данные передаем параметрами. В итоге кнопка будет такой:<Button Content="Изменить изображение" Command="{Binding ChangeImageCommand}" CommandParameter="{Binding}" />Но тут есть проблема. Кнопка внутри коллекции, а значит источником данных будет являться конкретный объект коллекции, а не основная VM. Чтобы это обойти, надо найти предка и через него выйти к нужному классу данных. Для MAUI есть отличная документация, которая это все показывает.
Сама команда будет простым методом с аргументом самого объекта, который меняется. При использовании
CommunityToolkit.MVVMтак и вовсе, это превратиться в[RelayCommand] private void ChangeImage(Installation installation) { installation.ImageUri = ...; }
На этом, пожалуй все. Удачи в изучении!




