Как сделать ссылку в wpf
Как сделать простую гиперссылку в XAML?
все, что я хочу сделать, это сделать небольшую гиперссылку в XAML. Я перепробовал все. Я сдаюсь.
каков синтаксис для этого?
Visual Studio Team: в Visual Studio 2010 Я хочу, чтобы Клиппи выскочил и сказал: «Кажется, вы пытаетесь сделать гиперссылку» и скажите мне, как это сделать. Ты не можешь сделать это с Меф? Было бы ретро круто, и эти маленькие» как мне сделать то, что я уже знаю, как делать в HTML » проблемы сжигают так много времени во время обучения процесс с XAML.
8 ответов
вы можете использовать кнопку с шаблоном пользовательского элемента управления, код ниже-ограниченная кнопка стиля гиперссылки (например, она поддерживает только текстовые гиперссылки), но, возможно, она укажет вам в правильном направлении.
как только вы поймете, что есть две отдельные иерархии классов с различным поведением макета, имеет смысл, что гиперссылка будет на «текстовой» стороне вещей (позволяет легко, например, иметь абзац с гиперссылкой посередине, и даже для этой гиперссылки обернуть через разрыв строки).
но нет,это не так легко обнаружить, когда вы начинаете.
чтобы смешать два мира и использовать гиперссылку в качестве элемента управления, все, что вам нужно сделать, это поместить его в TextBlock. TextBlock-это контрольная вещь (т. е. может идти в StackPanel), которая содержит текстовые вещи (т. е. может содержать гиперссылку):
вы можете обнаружить, что если вы привязываетесь к чему-либо, кроме простых текстовых значений, вам нужно будет использовать ContentPresenter в противном случае ничего не появится, это может быть верно, если вы привязываетесь к источнику данных XML.
триггер свойства для IsMouseOver придает тексту подчеркивание.
ниже приведен пример, где I»m привязка к XML.
это свяжет любой привязанный текст во вложенном textblock, я еще не нашел лучшего способа, я хотел бы, чтобы первый textblock не был там, если это возможно. Это будет работать и для DataTemplates.
вы можете просто использовать HyperlinkButton. При щелчке на URL-адрес будет отображаться в вашем веб-браузере:
как правило, понятие гиперссылки, чтобы Отдать якоря, чтобы отправить пользователя на другую страницу или вообще на другой ресурс, поэтому он реализован таким образом, и вы должны указать местоположение этого ресурса такой:
тем не менее, я нашел этот блог post с пользовательским TextBlock, который используется в качестве гиперссылки и поддерживает события click.
Быстрый старт с WPF. Часть 1. Привязка, INotifyPropertyChanged и MVVM
По разным причинам большинство из нас использует десктопные приложения, как минимум, браузер 🙂 А у некоторых из нас возникает необходимость в написании своих. В этой статье я хочу пробежаться по процессу разработки несложного десктопного приложения с использованием технологии Windows Presentation Foundation (WPF) и применением паттерна MVVM. Желающих продолжить чтение прошу под кат.
В чём особенность WPF?
Два основных отличия WPF от других средств построения десктопных приложений:
Я не буду углубляться в подробности, т.к. это не совсем тема статьи. Если интересно, то гуглить XAML, WPF rendering, milcore.dll и DirectX 🙂
О чём эта статья?
Эта статья содержит пример приложения, построенного на технологии WPF:
Я постараюсь ориентировать материал статьи в практическую сторону в стиле «повторяй за мной» с пояснениями.
Что нам понадобится для повторения статьи?
Так же в этом разделе я опишу создание проекта.
Запускаем VS, создаём новый проект, тип приложения выбираем WPF App (.NET Framework) (можно ввести в строке поиска справа вверху), называем как угодно.
После создания нового проекта откроется окно редактора интерфейса, у меня оно выглядит так
Внизу находится редактор разметки, вверху — предпросмотр интерфейса окна, но можно поменять относительное расположение редактора кода и предпросмотра интерфейса так, что они будут располагаться в горизонтальном порядке, с помощью вот этих кнопок (справа на границе двух областей):
Перед тем, как начать
Элементы окна (их ещё называют контрОлами от слова Control) должны размещаться внутри контейнера или внутри другого элемента типа ContentControl. Контейнер — это специальный контрол, позволяющий разместить внутри себя несколько дочерних контролов и организовать их взаимное расположение. Примеры контейнеров:
Есть и другие контейнеры. Поскольку контейнер тоже является контролом, то внутри контейнера могут быть вложенные контейнеры, содержащие вложенные контейнеры и так далее. Это позволяет гибко располагать контролы относительно друг друга. Так же с помощью контейнеров мы можем не менее гибко управлять поведением вложенных контролов при изменении размеров окна.
MVVM и интерфейс INotifyPropertyChanged. Копия текста.
Итогом этого примера станет приложение с двумя контролами, в одном из которых можно редактировать текст, а в другом только просматривать. Изменения из одного в другой будут переходить синхронно без явного копирования текста с помощью привязки (binding).
Теперь сосредоточимся на цели этого примера. Мы хотим, чтобы при наборе текста в текстбоксе этот же текст синхронно отображался в текстблоке, избежав при этом явной операции копирования текста. Нам понадобится некая связующая сущность, и вот тут-то мы и подошли к такой штуке, как привязка (binding), о которой было сказано выше. Привязка в терминологии WPF — это механизм, позволяющий связывать некоторые свойства контролов с некоторыми свойствами объекта C#-класса и выполнять взаимное обновление этих свойств при изменении одной из частей связки (это может работать в одну, в другую или в обе стороны сразу). Для тех, кто знаком с Qt, можно провести аналогию слотов и сигналов. Чтобы не растягивать время, перейдём к коду.
Итак, для организации привязки нужны свойства контролов и некое свойство некоего C#-класса. Для начала разберёмся с XAML-кодом. Текст обоих контролов хранится в свойстве Text, поэтому добавим привязку для этих свойств. Делается это так:
Мы сделали привязку, но пока непонятно к чему 🙂 Нам нужен объект какого-то класса и какое-то свойство в этом объекте, к которому будет выполнена привязка (как ещё говорят, на которое нужно забиндиться).
Так что это за класс? Этот класс называется вьюмоделью (view model) и служит как раз связующим звеном между view (интерфейсом или его частями) и model (моделью, т.е. теми частями кода, которые отвечают за логику приложения. Это позволяет отделить (в какой-то степени) логику приложения от интерфейса (представления, view) и называется паттерном Model-View-ViewModel (MVVM). В рамках WPF этот класс также называется DataContext.
Однако, просто написать класс вьюмодели недостаточно. Нужно ещё как-то оповещать механизм привязки о том, что свойство вьюмодели или свойство вью изменилось. Для этого существует специальный интерфейс INotifyPropertyChanged, который содержит событие PropertyChanged. Реализуем этот интерфейс в рамках базового класса BaseViewModel. В дальнейшем все наши вьюмодели мы будем наследовать от этого базового класса, чтобы не дублировать реализацию интерфейса. Итак, добавим в проект каталог ViewModels, а в этот каталог добавим файл BaseViewModel.cs. Получим такую структуру проекта:
Код реализации базовой вьюмодели:
Создадим для нашего класса MainWindow свою вьюмодель, унаследовавшись от базовой. Для этого в том же каталоге ViewModels создадим файл MainWindowViewModel.cs, внутри которого будет такой код:
Шикарно! Теперь нужно добавить в эту вьюмодель свойство, на которое будем биндить текст наших контролов. Поскольку это текст, тип этого свойства должен быть string:
В итоге получим такой код
Так, кажется, справились. Осталось забиндиться на это свойство из вьюхи и готово. Давайте сделаем это прямо сейчас:
Ништяк, запускаем проект, набираем текст в текстбокс иииии… ничего не происходит))) Ну, ничего страшного, на самом деле мы идём правильной дорогой, просто пока ещё не дошли до нужной точки.
Предлагаю на минутку остановиться и подумать, чего же нам не хватает. Вьюха у нас есть. Вьюмодель тоже. Свойства вроде забиндили. Нужный интерфейс реализовали. Проделали кучу работы ради копирования жалкой строчки текста, за что нам это. 111
Ладно, шутки в сторону. Мы забыли создать объект вьюмодели и кое-что ещё (об этом позже). Сам класс мы описали, но это ничего не значит, ведь у нас нет объектов этого класса. Ок, где нужно хранить ссылку на этот объект? Ближе к началу примера я упомянул некий DataContext, используемый в WPF. Так вот, у любой вью есть свойство DataContext, которому мы можем присвоить ссылку на нашу вьюмодель. Сделаем это. Для этого откроем файл MainWindow.xaml и нажмём F7, чтобы открыть код этой вьюхи. Он практически пустой, в нём есть только конструктор класса окна. Добавим в него создание нашей вьюмодели и поместим её в DataContext окна (не забываем добавить using с нужным неймспейсом):
Это было просто, но этого всё равно не хватает. По-прежнему при запуске приложения никакой синхронизации текста не происходит. Что ещё нужно сделать?
Нужно вызвать событие PropertyChanged при изменении свойства SynchronizedText и сообщить вьюхе о том, что она должна следить за этим событием. Итак, чтобы вызвать событие, модифицируем код вьюмодели:
Что мы тут сделали? Добавили скрытое поле для хранения текста, обернули его в уже существующее свойство, а при изменении этого свойства не только меняем скрытое поле, но и вызываем метод OnPropertyChanged, определённый в базовой вьюмодели и вызывающий событие PropertyChanged, объявленное в интерфейсе INotifyPropertyChanged, так же реализованное в базовой вьюмодели. Получается, что при каждом изменении текста возникает событие PropertyChanged, которому передаётся имя свойства вьюмодели, которое было изменено.
Ну, почти всё, финишная прямая! Осталось указать вьюхе, что оно должно слушать событие PropertyChanged:
Помимо того, что мы указали, по какому триггеру должно происходить обновление, мы так же указали, в какую сторону это обновление отслеживается: от вью к вьюмодели или наоборот. Поскольку в текстбоксе мы вводим текст, то нам интересны изменения только во вью, поэтому выбираем режим OneWayToSource. В случае с текстблоком всё ровно наоборот: нам интересны изменения во вьюмодели, чтобы отобразить их во вью, поэтому выбираем режим OneWay. Если бы нам нужно было, чтобы изменения отслеживались в обе стороны, можно было не указывать Mode вообще, либо указать TwoWay явно.
Итак, запускаем программу, набираем текст и voi-la! Текст синхронно меняется, и мы нигде ничего не копировали!
Спасибо за внимание, продолжение следует. Будем разбираться с DataTemplate и паттерном Command.
Хронология страниц
Теперь, когда уже известно о страницах и различных способах их размещения, можно переходить к более подробному рассмотрению используемой WPF модели навигации. В этой статье речь пойдет о том, как именно работают гиперссылки WPF и каким образом восстанавливаются страницы, когда пользователь снова к ним возвращается.
Вам наверняка интересно узнать, как в действительности работают свойства вроде Application.StartupUri, Frame.Source и Hyperlink.NavigateUri. В приложении, которое состоит из несвязанных XAML-файлов и выполняется в браузере, этот процесс выглядит довольно просто: при щелчке на гиперссылке браузер интерпретирует ссылку на страницу как относительный URI-адрес и ищет указанную XAML-страницу в текущей папке. Но в скомпилированном приложении страницы перестают быть доступными в виде отдельных ресурсов: они компилируются в BAML (Binary Application Markup Language — двоичный язык разметки приложений) и вставляются в сборку. Так как же на них ссылаться с помощью URI?
Эта система работает благодаря способу, которым WPF обращается к ресурсам приложения. При выполнении щелчка на гиперссылке в скомпилированном XAML-приложении URI все равно интерпретируется как относительный путь. Однако он является относительным по отношению к базовому URI приложения. Поэтому гиперссылка, указывающая на Page1.xaml, фактически преобразуется в следующий упакованный URI:
Может возникнуть вопрос: почему так важно знать, как работают URI-адреса гиперссылок, если весь процесс проходит столь гладко? Главная причина состоит в том, что может потребоваться создать приложение, позволяющее переходить на XAML-страницы, которые хранятся в другой сборке. На самом деле для принятия такого проектного решения имеются веские основания. Поскольку страницы могут применяться в разных контейнерах, может возникнуть желание повторно использовать один и тот же набор страниц как в приложении ХВАР, так и в обычном приложении Windows.
В таком случае можно развернуть просто две версии приложения — браузерную и настольную. Чтобы избежать дублирования кода, все страницы, которые планируется использовать повторно, следует поместить в отдельную сборку библиотеки классов (DLL), на которую затем можно сослаться в обоих проектах приложений.
Это потребует внесения изменения в URI-адреса. При наличии страницы в одной сборке, указывающей на страницу в другой сборке, нужно будет использовать следующий синтаксис:
Конечно, абсолютный путь вряд ли будет использоваться. Вместо него гораздо целесообразнее применять в URI-адресах следующий относительный путь:
При создании сборки SharedLibrary для получения правильных ссылок на сборки, импортированных пространств имен и настроек приложения лучше использовать шаблон проекта Custom Control Library (WPF) (Библиотека специальных элементов управления (WPF)).
Хронология навигации
Хронология страниц в WPF работает точно так же, как и в браузере. При каждом переходе на новую страницу текущая страница добавляется в список предыдущих страниц. При щелчке на кнопке возврата страница добавляется в список следующих страниц. В случае возврата на одну страницу и перехода с нее на новую страницу список следующих страниц очищается.
Поведение списков предыдущих и следующих страниц выглядит довольно просто, но внутренние механизмы, обеспечивающие работу этих списков, являются гораздо более сложными. Например, предположим, что вы посещаете страницу с двумя текстовыми полями, вводите в них что-нибудь и двигаетесь дальше. Если вы после этого вернетесь обратно на эту страницу, то увидите, что WPF восстанавливает состояние текстовых полей, т.е. в них отображается все, что было ранее введено.
Между возвращением на страницу через хронологию навигации и выполнением щелчка на ссылке, которая направляет на эту же самую страницу, существует большая разница. Например, при переходе со страницы Page1 на страницу Раде2 и затем снова на страницу Page1 с помощью соответствующих ссылок WPF создаст три отдельных объекта страницы. При втором отображении страница Page1 создастся как отдельный экземпляр с собственным состоянием. Однако в случае возврата к первому экземпляру Page1 за счет двукратного щелчка на кнопке возврата она будет видна в исходном состоянии.
Может показаться, что WPF поддерживает состояние ранее посещенных страниц за счет удержания объекта страницы в памяти. Проблема такого подхода состоит в том, что связанные с памятью накладные расходы в сложном приложении с множеством страниц в таком случае могут быть слишком большими. По этой причине удержание объекта страницы не считается безопасной стратегией и вместо него при покидании страницы сохраняется информация о состоянии всех элементов управления, а объект страницы уничтожается. При возврате на эту страницу WPF создает ее заново (из исходного XAML-файла) и восстанавливает состояние ее элементов управления.
Такая стратегия сопровождается меньшим количеством накладных расходов, поскольку для сохранения деталей о состоянии элементов управления требуется гораздо меньше памяти, чем для сохранения всей страницы вместе с визуальным деревом объектов.
Глядя на эту систему, возникает интересный вопрос: как WPF решает, какие детали нужно сохранять? WPF анализирует все дерево элементов страницы и просматривает имеющиеся у этих элементов свойства зависимости. Свойства, которые должны быть сохранены, имеют небольшой фрагмент дополнительных метаданных — журнальный флаг, который указывает, что они должны помещаться в журнал навигации. Этот флаг устанавливается с помощью объекта FrameworkPropertyMetadata при регистрации свойства зависимости.
Присмотревшись к системе навигации поближе, можно будет заметить, что у многих свойств нет журнального флага. Например, если установить свойство Content элемента управления содержимым или свойство Text элемента TextBlock с помощью кода, ни одна из этих деталей не будет восстановлена при возврате на страницу. То же самое будет и при динамической установке свойства Foreground или Background. Однако если установить свойство Text элемента TextBox, свойство IsSelected элемента CheckBox или свойство SelectedIndex элемента ListBox, то все эти детали сохранятся.
Так что же можно предпринять, если такое поведение не подходит? Как быть в случае установки множества свойств динамическим образом и желании, чтобы вся эта информация сохранялась на страницах? Существует несколько возможных вариантов.
Самый мощный предполагает применение свойства Page.KeepAlive, которое по умолчанию имеет значение false. Когда это свойство устанавливается в true, WPF не применяет описанный выше механизм сериализации. Вместо этого WPF оставляет объекты всех страниц в действующем состоянии. Благодаря этому, при возврате обратно страница оказывается в том же виде, в котором была ранее. Разумеется, у этого варианта есть один недостаток, заключающийся в увеличении накладных расходов, связанных с памятью, поэтому его следует применять только для нескольких страниц, которые действительно в нем нуждаются.
В случае использования свойства KeepAlive для сохранения страницы в действующем состоянии, при следующем возврате к ней событие Initialized генерироваться не будет. (Для страниц, которые не сохраняются в действующем состоянии, а «возвращаются к жизни» с помощью системы журнализации WPF, такое событие будет инициироваться при каждом их посещении пользователем.) Если такое поведение не подходит, тогда следует обработать события Unloaded и Loaded, которые генерируются всегда.
Второй вариант — построить другое проектное решение, способное передавать информацию, где это необходимо. Например, можно создать страничные функции, предназначенные для возврата информации. Используя страничные функции вместе с дополнительной логикой инициализации, можно разработать собственную систему для извлечения из страницы важной информации и ее сохранения в подходящем месте.
С хронологией навигации WPF связан еще один недостаток. Можно написать код, который будет динамически создавать объект страницы и затем переходить на нее. В такой ситуации обычный механизм сохранения состояния страницы работать не будет. У WPF нет ссылки на XAML-документ страницы, поэтому ей не известно, как реконструировать страницу. (А если страница создается динамически, у нее может вообще не быть соответствующего XAML-документа.) В такой ситуации WPF всегда будет сохранять объект страницы в памяти, каким бы ни было значение свойства KeepAlive.
Добавление специальных свойств
Обычно все поля в классе страницы утрачивают свои значения при уничтожении данной страницы. Чтобы добавить в класс страницы какие-то специальные свойства и сделать так, чтобы они сохраняли свои значения, можно соответствующим образом устанавливать журнальный флаг. Однако подобное действие в отношении обычного свойства или поля невозможно. Поэтому в таком случае необходимо создать в классе страницы свойство зависимости.
Для создания свойства зависимости потребуется выполнить два шага. Во-первых, необходимо создать определение свойства зависимости, а во-вторых — добавить процедуру обычного свойства, устанавливающую или извлекающую значение этого свойства зависимости.
Чтобы определить свойство зависимости, необходимо создать статическое поле, такое как показанное ниже:
По соглашению поле, определяющее свойство зависимости, должно обязательно иметь то же имя, что и обычное свойство, но со словом Property в конце.
В данном примере используется приватное свойство зависимости. Причина в том, что единственный код, которому требуется получать доступ к этому свойству, находится в классе страницы, где это свойство определено.
В завершение необходим статический конструктор, регистрирующий определение свойства зависимости. Именно здесь указываются службы, которые должны применяться со свойством зависимости (вроде поддержки привязки данных, анимации и журнализации):
Затем можно создавать обычное свойство, упаковывающее данное свойство зависимости. Однако при написании процедур извлечения и установки следует использовать методы GetValue() и SetValue(), определенные в базовом классе DependencyObject:
Осталось только добавить все эти детали в одну страницу (в данном примере это PageWithPersistentData). После этого значение свойства MyPageData будет автоматически сериализироваться, когда пользователь покидает страницу, и восстанавливаться при его возвращении.