Расположение объекта xaml в отдельный файл проекта
Доброго времени суток всем)
Не знаю, как точно сформулировать свой вопрос, поэтому попытаюсь описать его очень детально.
У меня есть в проекта xaml-код основного окна, и, соответственно, там же расположен визуальный редактор. У меня есть задача нарисовать достаточное большое кол-во разных фигур (и в будущем их число будет увеличиваться). Все эти фигуры будут вызываться в ходе работы программы в нном кол-ве. Я хотел бы(если что-то подобное в VS вообще есть), сохранять каждую фигуры в отдельный xaml файл, и позже их использовать програмно.
Я мог бы нарисовать все эти фигуры в основном коде xaml, и поставить Visible=Collapsed, а позже копировать эти фигуры в коде, и изменять их размер и изображение. Но этот способ максимально недубен. К тому же, не хотелось бы все фигуры хранить в основном коде xaml, чтобы не нагружать этот код.
И сразу вторую часть вопроса хотел бы задать. Если способ хранить фигуры в отдельных xaml файлах (которые будут расположены в папке в моей текущем проекте) есть (в чем я почти уверен, просто не знаю, как это сделать), то есть ли возможность добавлять отдельные xaml файлы в проект, но вместе с их визуальным редактором?
(Как я себе это представляю, чисто теоретически):
Я кликаю ЛКМ на папку в проекте, нажимаю добавить. Далее я выбираю что-то, что добавит мне xaml код с визуальным редактором. (как xaml код с редактором окна, только без связи с програмной частю, пока-что я сам не задействую xaml-код програмно).
Вооот. Надеюсь я смог адекватно объяснить свою мысль. Спасибо большой тем, кто дочитал сюда:)
Буду очень благодарен, если кто сможет как-то помочь.
Ответы (1 шт):
Я вам покажу самый простейший вариант того, как размещать всякие фигуры правильно в WPF проекте, без мудрения с сохранением XAML и прочего.
Суть - отделить данные от визуальной части, вот просто представьте, что вы рисуете, к примеру в фотошопе, вы запускаете программу, грузите .psd файл, программа прогружает все слои, все ваши рисунки, наброски, и прочее. Сейчас же, вы пытаетесь, грубо говоря сохранить сделанный вами снимок экрана, на котором был запущен фотошоп и нарисована картинка. Согласитесь, звучит бредово. Вот и ваша задумка с сохранение XAML тоже, весьма странная, ведь XAML создан для того, чтобы вывести конкретный объект так задумал его разработчик, а не как формат для хранения данных.
Ок, поняв суть проблемы, давайте реализовывать задумку:
Создадим простой класс, в котором будет храниться та информация, которая нужна нам для вывода объекта на экран, допустим это будет цвет и положение (X/Y), фигура пока пусть будет простой круг:
public class Circle { public double X { get; set; } public double Y { get; set; } public Brush Fill { get; set; } }Как видите, примитивные данные, ничего лишнего.
Теперь сделаем коллекцию в виде свойства, в которую сразу добавим несколько наших кругов, писать это будет прям в классе окна, но вам советую сделать отдельный класс для данных:
public List<Circle> Shapes { get; set; } = new() { new(){ Fill = Brushes.Black, X = 0, Y = 0 }, new(){ Fill = Brushes.Red, X = 30, Y = 30 }, new(){ Fill = Brushes.Green, X = 60, Y = 60 }, new(){ Fill = Brushes.Purple, X = 90, Y = 90 }, };Обратите внимание, это свойство, ведь привязка доступна только к публичным свойствам. Также заметьте, тут простой
List<>, если вы будете добавлять данные в момент работы приложения, то лучше заменить наObservableCollection<>, иначе интерфейс не будет оповещаться об изменениях.Теперь надо окну задать источник данных, из которого он будет брать свойства для привязки, пишем прям в конструкторе, после инициализации
DataContext = this;, но опять, это чисто примитивный пример, так лучше не делать на практике, класс окна у вас должен быть чистым, аDataContextзадан до момента инициализации окна (почему и как).Супер, у нас есть теперь данные, осталось вывести их на экран. Открываем XAML окна и пишем нечто такое:
<ItemsControl ItemsSource="{Binding Shapes}"> <ItemsControl.ItemTemplate> <DataTemplate> <Ellipse Width="30" Height="30" Fill="{Binding Fill}" /> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Left" Value="{Binding X}" /> <Setter Property="Canvas.Top" Value="{Binding Y}" /> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl>Здесь мы берем
ItemsControl, который выводит все объекты коллекции на экран и привязываем его к нашей созданной коллекции; далее задаем черезItemTemplateнужный вид с привязкой к нужным свойствам; далее черезItemsPanelзадаем панель, на который будут размещены объекты (Canvas); ну и последнее, черезItemContainerStyleзадаем им позицию наCanvas.
Запускаем, смотрим на результат:
Как видите, мы без проблем отделили данные от визуальной части, нам не нужно теперь лезть в XAML для того, чтобы добавить, к примеру новый круг, все это просто делается добавлением нового объекта в коллекцию.
Как нам улучшить приложение, если должно быть несколько фигур?
А по сути также, только нам надо сделать еще один класс, вынести общую логику в абстракцию/интерфейс, ну и дополнить XAML.
Вынесем в интерфейс всю общую информацию фигуры. Допустим, мы хотим сделать квадрат, что у него общего с кругом? Цвет, позиция, по сути, в моем примере он дублирует ранее созданный нами класс
Circle, давайте тогда вынесем это вinterfacepublic interface IShape { public double X { get; } public double Y { get; } public Brush Fill { get; } }Унаследуем круг от созданного интерфейса:
public class Circle : IShape { public double X { get; set; } public double Y { get; set; } public Brush Fill { get; set; } }Создадим новый класс, допустим квадрат, который также будет унаследован от
IShape:public class Square : IShape { public double X { get; set; } public double Y { get; set; } public Brush Fill { get; set; } }Изменим коллекцию объектов, пусть там будет несколько квадратов и кругов, а сама коллекция будет уже
List<IShape>:public List<IShape> Shapes { get; set; } = new() { new Circle(){ Fill = Brushes.Black, X = 0, Y = 0 }, new Circle(){ Fill = Brushes.Red, X = 30, Y = 30 }, new Square(){ Fill = Brushes.Green, X = 60, Y = 60 }, new Square(){ Fill = Brushes.Purple, X = 90, Y = 90 }, };Теперь XAML, нам надо вынести вид объектов из
ItemTemplateв ресурсы:Удаляем
<ItemsControl.ItemTemplate>, запомнив егоDataTemplate.Пишем на его месте
<ItemsControl.Resources>и помещаем внутрьDataTemplate. Вы можете поместить их в ресурсы окна, задав окну (Window.Resources), или можете поместить вApp.xamlфайл, что будет ресурсом приложения, либо вообще вынести в отдельный проект, это уже решайте сами. Должно быть примерно следующее:<ItemsControl.Resources> <DataTemplate> <Ellipse Width="30" Height="30" Fill="{Binding Fill}" /> </DataTemplate> </ItemsControl.Resources>
Вынеся нужный вид в ресурсы, мы теперь можем их задавать под тип входного объекта, делается это при помощи указания
DataTypeуDataTemplate. Давайте пропишем квадрату и кругу все нужное:<ItemsControl.Resources> <DataTemplate DataType="{x:Type local:Circle}"> <Ellipse Width="30" Height="30" Fill="{Binding Fill}" /> </DataTemplate> <DataTemplate DataType="{x:Type local:Square}"> <Rectangle Width="30" Height="30" Fill="{Binding Fill}" /> </DataTemplate> </ItemsControl.Resources>
Запускаем, смотрим результат:
Как видите, теперь у нас 2 фигуры, данные которых в одном месте, а вид в другом. Имея все это, вы теперь без проблем сможете выбрать нужный вам тип хранения, будь то база данных, или простая сериализация в конкретный тип, это уже решайте сами, главное, не работайте с XAML, как с источником данных. Если вам нужно видеть эти изменения и в дизайнере студии, то для этого задайте окну d:DataContext="{d:DesignInstance {x:Type local:MainWindow}, IsDesignTimeCreatable=True}", где IsDesignTimeCreatable - это подгрузка реальных данных, но учтите, этот способ может сильно нагрузить студию, особенно когда данных много. Чтобы не грузить все данные, можете сделать специальные данные для дизайнера.

